Merge branch 'master' into dynamic_divider

This commit is contained in:
Vitaliy 2020-10-05 11:13:51 +03:00
commit 1b930fdadd
73 changed files with 3639 additions and 1455 deletions

4
.gitignore vendored
View file

@ -19,6 +19,10 @@ OsmAndCore_*.aar
.project
out/
# Huawei
agconnect-services.json
OsmAndHms.jks
# Android Studio
/.idea
*.iml

View file

@ -739,7 +739,7 @@ public class RoutePlannerFrontEnd {
res = searchRouteImpl(ctx, points, routeDirection);
}
if (ctx.calculationProgress != null) {
ctx.calculationProgress.timeToCalculate += (System.nanoTime() - timeToCalculate);
ctx.calculationProgress.timeToCalculate = (System.nanoTime() - timeToCalculate);
}
BinaryRoutePlanner.printDebugMemoryInformation(ctx);
if (res != null) {

View file

@ -75,7 +75,7 @@
<string name="by_distance">По расстоянию</string>
<string name="by_name">По имени</string>
<string name="by_group">По группе</string>
<string name="shared_string_sort">Сортировать</string>
<string name="shared_string_sort">Сортировка</string>
<string name="shared_string_sort_by">Сортировать по</string>
<string name="turn_off_all">Отстановить все</string>
<string name="shared_string_exit">Выход</string>

3
OsmAnd/.gitignore vendored
View file

@ -13,10 +13,13 @@ libs/it.unibo.alice.tuprolog-tuprolog-3.2.1.jar
libs/commons-codec-commons-codec-1.11.jar
libs/OsmAndCore_android-0.1-SNAPSHOT.jar
# Huawei
libs/huawei-*.jar
huaweidrmlib/
HwDRM_SDK_*
drm_strings.xml
agconnect-services.json
OsmAndHms.jks
# copy_widget_icons.sh
res/drawable-large/map_*

View file

@ -2,24 +2,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity android:name="com.huawei.android.sdk.drm.DrmDialogActivity"
android:configChanges="screenSize|orientation|keyboardHidden"
android:exported="false"
android:theme="@android:style/Theme.Translucent">
<meta-data
android:name="hwc-theme"
android:value="androidhwext:style/Theme.Emui.Translucent" />
</activity>
<application
android:icon="@mipmap/icon_free"
android:label="@string/app_name_free"
tools:replace="android:icon, android:label">
<activity
android:name="net.osmand.plus.activities.MapActivity"
android:theme="@style/FirstSplashScreenFree"
tools:replace="android:theme"/>
<service
android:name="net.osmand.plus.NavigationService"
tools:replace="android:process"
android:process="net.osmand.huawei"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="net.osmand.huawei.fileprovider"
tools:replace="android:authorities" />
<service
android:name="net.osmand.plus.NavigationService"
android:process="net.osmand.huawei"
tools:replace="android:process" />
tools:replace="android:authorities"
android:authorities="net.osmand.huawei.fileprovider"/>
</application>
</manifest>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity android:name="com.huawei.android.sdk.drm.DrmDialogActivity"
android:configChanges="screenSize|orientation|keyboardHidden"
android:exported="false"
android:theme="@android:style/Theme.Translucent">
<meta-data
android:name="hwc-theme"
android:value="androidhwext:style/Theme.Emui.Translucent" />
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="net.osmand.plus.huawei.fileprovider"
tools:replace="android:authorities" />
<service
android:name="net.osmand.plus.NavigationService"
android:process="net.osmand.plus.huawei"
tools:replace="android:process" />
</application>
</manifest>

View file

@ -40,6 +40,13 @@ android {
keyAlias "osmand"
keyPassword System.getenv("OSMAND_APK_PASSWORD")
}
publishingHuawei {
storeFile file("/var/lib/jenkins/osmand_hw_key")
storePassword System.getenv("OSMAND_HW_APK_PASSWORD")
keyAlias "osmand"
keyPassword System.getenv("OSMAND_HW_APK_PASSWORD")
}
}
defaultConfig {
@ -107,19 +114,23 @@ android {
debug {
manifest.srcFile "AndroidManifest-debug.xml"
}
full {
java.srcDirs = ["src-google"]
}
free {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-free.xml"
}
freedev {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-freedev.xml"
}
freecustom {
java.srcDirs = ["src-google"]
manifest.srcFile "AndroidManifest-freecustom.xml"
}
huawei {
manifest.srcFile "AndroidManifest-huawei.xml"
}
freehuawei {
java.srcDirs = ["src-huawei"]
manifest.srcFile "AndroidManifest-freehuawei.xml"
}
@ -189,17 +200,12 @@ android {
dimension "version"
applicationId "net.osmand.plus"
resConfig "en"
//resConfigs "xxhdpi", "nodpi"
}
huawei {
dimension "version"
applicationId "net.osmand.plus.huawei"
// resConfigs "xxhdpi", "nodpi"
}
freehuawei {
dimension "version"
applicationId "net.osmand.huawei"
}
// CoreVersion
legacy {
dimension "coreversion"
@ -219,9 +225,13 @@ android {
signingConfig signingConfigs.development
}
release {
if (gradle.startParameter.taskNames.toString().contains("huawei")) {
signingConfig signingConfigs.publishingHuawei
} else {
signingConfig signingConfigs.publishing
}
}
}
}
@ -276,46 +286,14 @@ task downloadWorldMiniBasemap {
}
}
task downloadHuaweiDrmZip {
task setupHuaweiConfig {
doLast {
ant.get(src: 'https://obs.cn-north-2.myhwclouds.com/hms-ds-wf/sdk/HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'HwDRM_SDK_2.5.2.300_ADT.zip', skipexisting: 'true')
ant.unzip(src: 'HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'huaweidrmlib/')
if (System.getenv("HUAWEI_SDK_JSON")) {
new File("agconnect-services.json").text = System.getenv("HUAWEI_SDK_JSON")
}
}
task copyHuaweiDrmLibs(type: Copy) {
dependsOn downloadHuaweiDrmZip
from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/libs"
into "libs"
}
task copyHuaweiDrmValues(type: Copy) {
dependsOn downloadHuaweiDrmZip
from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/res"
into "res"
}
task downloadPrebuiltHuaweiDrm {
dependsOn copyHuaweiDrmLibs, copyHuaweiDrmValues
}
task cleanHuaweiDrmLibs(type: Delete) {
delete "huaweidrmlib"
delete fileTree("libs").matching {
include "**/huawei-*.jar"
}
}
task cleanHuaweiDrmValues(type: Delete) {
delete fileTree("res").matching {
include "**/drm_strings.xml"
}
}
task cleanPrebuiltHuaweiDrm {
dependsOn cleanHuaweiDrmLibs, cleanHuaweiDrmValues
}
task collectVoiceAssets(type: Sync) {
from "../../resources/voice"
into "assets/voice"
@ -397,8 +375,6 @@ task copyLargePOIIcons(type: Sync) {
}
}
task copyWidgetIconsXhdpi(type: Sync) {
from "res/drawable-xxhdpi/"
into "res/drawable-large-xhdpi/"
@ -445,13 +421,9 @@ task collectExternalResources {
copyPoiCategories,
downloadWorldMiniBasemap
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase()
// Use Drm SDK only for huawei build
String tskReqStr = gradle.startParameter.taskNames.toString()
if (tskReqStr.contains("huawei")) {
dependsOn downloadPrebuiltHuaweiDrm
} else {
dependsOn cleanPrebuiltHuaweiDrm
dependsOn setupHuaweiConfig
}
}
@ -503,10 +475,16 @@ task cleanupDuplicatesInCore() {
file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so"))
}
}
afterEvaluate {
android.applicationVariants.all { variant ->
variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore)
}
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase()
if (tskReqStr.contains("huawei")) {
apply plugin: 'com.huawei.agconnect'
}
}
task appStart(type: Exec) {
@ -516,7 +494,6 @@ task appStart(type: Exec) {
// commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity'
}
dependencies {
implementation project(path: ':OsmAnd-java', configuration: 'android')
implementation project(':OsmAnd-api')
@ -565,6 +542,5 @@ dependencies {
}
implementation 'com.jaredrummler:colorpicker:1.1.0'
huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar')
freehuaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar')
freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300'
}

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="6dp"
android:height="9dp"
android:viewportWidth="12"
android:viewportHeight="6">
android:viewportHeight="9">
<path
android:pathData="M0,6L6,0L12,6H0Z"
android:pathData="M0,9L6,0L12,9L6,6L0,9Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -721,9 +721,9 @@
<string name="gpx_option_reverse_route">عكس اتجاه المسار</string>
<string name="gpx_option_destination_point">استخدم الوجهة الحالية</string>
<string name="gpx_option_from_start_point">يمر على طول المسار باكمله</string>
<string name="switch_to_vector_map_to_see">خريطة التنقل متوفرة حالياً لهذا الموقع.
<string name="switch_to_vector_map_to_see">خريطة التنقل متوفرة لهذا الموقع فعلها عبر
\n
\nلتفعليها \'القائمة\' ← \'ضبط الخريطة\' ← \'مصدر الخريطة\' ← \'الخريطة المحملة\'.</string>
\n\'القائمة\' ← \'ضبط الخريطة\' ← \'مصدر الخريطة\' ← \'الخريطة المحملة\'.</string>
<string name="choose_audio_stream">مصدر التوجيه الصوتي</string>
<string name="choose_audio_stream_descr">اختيار قناة لتشغيل التوجيه الصوتي.</string>
<string name="voice_stream_voice_call">صوت المكالمة الهاتفية ( كما يحاول قطع ستريو بلوتوث السيارة )</string>
@ -1645,7 +1645,7 @@
<string name="road_blocked">الطريق محظور</string>
<string name="shared_string_select">تحديد</string>
<string name="switch_start_finish">اعكس نقطة الانطلاق والوصول</string>
<string name="rendering_attr_hideIcons_name">أيقونات POI</string>
<string name="rendering_attr_hideIcons_name">أيقونات نقاط الاهتمام</string>
<string name="shared_string_type">النوع</string>
<string name="shared_string_not_selected">غير محدد</string>
<string name="rec_split">قسم مسجل</string>
@ -1972,7 +1972,7 @@
<string name="sd_unmounted">بطاقة الذاكرة غير متاحة.
\nلن تكون قادرا على رؤية الخرائط أو العثور على أماكن.</string>
<string name="sd_mounted_ro">بطاقة الذاكرة في وضع القراءة فقط.
\n يمكنك فقط مشاهدة الخريطة المحملة مسبقا ولا يمكنك التحميل من الإنترنت.</string>
\n يمكنك فقط مشاهدة الخريطة المحملة مسبقاً ولا يمكنك التحميل من الإنترنت.</string>
<string name="route_tshr">انعطف يميناً بشكل حاد</string>
<string name="route_tshl">انعطف يساراً بشكل حاد</string>
<string name="route_tu">قم بالدوران وواصل</string>

View file

@ -3756,8 +3756,8 @@
<string name="delete_address">Slet adresse</string>
<string name="add_address">Tilføj adresse</string>
<string name="access_hint_enter_address">Angiv adresse</string>
<string name="plan_route_trim_before">Trim før</string>
<string name="plan_route_trim_after">Trim efter</string>
<string name="plan_route_trim_before">Trimme før</string>
<string name="plan_route_trim_after">Trimme efter</string>
<string name="plan_route_change_route_type_before">Skift rutetype før</string>
<string name="plan_route_change_route_type_after">Skift rutetype efter</string>
<string name="simplified_track">Forenklet spor</string>
@ -3771,4 +3771,10 @@
<string name="shared_string_is_saved">er gemt</string>
<string name="one_point_error">Tilføj mindst to punkter.</string>
<string name="shared_string_redo">Omgøre</string>
<string name="navigate_to_track_descr">Navigere fra position til sporet</string>
<string name="pass_whole_track_descr">Punkt på sporet for at navigere</string>
<string name="number_of_gpx_files_selected_pattern">%s spor filer valgt</string>
<string name="sort_last_modified">Sidst ændret</string>
<string name="sort_name_descending">Navn: Z A</string>
<string name="sort_name_ascending">Navn: A Z</string>
</resources>

View file

@ -3876,9 +3876,7 @@
<string name="import_track_descr">Track Datei zum Folgen auswählen, oder vom Gerät importieren.</string>
<string name="disable_recording_once_app_killed_descrp">Die GPX-Aufzeichnung wird angehalten, wenn OsmAnd beendet wird (über „zuletzt verwendete Apps“). (Die Hintergrunddienst-Anzeige verschwindet aus der Android-Benachrichtigungsleiste.)</string>
<string name="save_global_track_interval_descr">Aufzeichnungsintervall für die generelle Track-Aufzeichnung festlegen (via Schaltfläche \'GPX\' auf dem Kartenbildschirm).</string>
<string name="route_between_points_warning_desc">Um diese Option nutzen zu können, muss OsmAnd den Track auf die Straßen der Karte einrasten.
\n
\n Wählen Sie im nächsten Schritt ein Navigationsprofil um festzulegen, welche Straßentypen verwendet werden sollen, und wählen Sie einen Wert für die maximal zulässige Entfernung zwischen Track und Straße.</string>
<string name="route_between_points_warning_desc">Als nächstes können Sie Ihren Track mit einem Ihrer Navigationsprofile auf die nächstgelegene erlaubte Straße einrasten lassen, um diese Option zu nutzen.</string>
<string name="quick_action_add_gpx">Track-Wegpunkt hinzufügen</string>
<string name="number_of_gpx_files_selected_pattern">%s Track Dateien ausgewählt</string>
<string name="simplified_track_description">Nur die Routenlinie wird gespeichert, die Wegpunkte werden gelöscht.</string>

View file

@ -3851,4 +3851,5 @@
<string name="poi_recycling_small_electrical_appliances">Pequeños electrodomésticos</string>
<string name="poi_beehive">Panal de abejas</string>
<string name="poi_nuts">Frutos secos</string>
<string name="poi_fuel_lng">Gas natural licuado</string>
</resources>

View file

@ -3907,4 +3907,5 @@
<string name="sort_last_modified">Último modificado</string>
<string name="sort_name_descending">Nombre: Z A</string>
<string name="sort_name_ascending">Nombre: A Z</string>
<string name="start_finish_icons">Iconos de inicio/fin</string>
</resources>

View file

@ -3851,4 +3851,5 @@
<string name="poi_departures_board">Tablero de partidas</string>
<string name="poi_nuts">Frutos secos</string>
<string name="poi_beehive">Panal de abejas</string>
<string name="poi_fuel_lng">Gas natural licuado</string>
</resources>

View file

@ -3904,4 +3904,5 @@
<string name="sort_name_descending">Nombre: Z A</string>
<string name="sort_name_ascending">Nombre: A Z</string>
<string name="sort_last_modified">Último modificado</string>
<string name="start_finish_icons">Iconos de inicio/fin</string>
</resources>

View file

@ -1588,7 +1588,7 @@
<string name="poi_memorial_koshinto">Koshinto</string>
<string name="poi_memorial_blue_plaque">Placa azul</string>
<string name="poi_memorial_jizo">Jizo</string>
<string name="poi_memorial_cross">Crucero (monumento)</string>
<string name="poi_memorial_cross">Cruz</string>
<string name="poi_historic_quarry">Cantera histórica</string>
<string name="poi_resource_aggregate">Agregado</string>
<string name="poi_resource_antimony">Antimonio</string>
@ -1825,7 +1825,7 @@
<string name="poi_training_martial_art">Capacitación: artes marciales</string>
<string name="poi_training_aviation">Capacitación: aviación</string>
<string name="poi_training_hairdressing">Capacitación: peluquería</string>
<string name="poi_monument_yes">Monumento</string>
<string name="poi_monument_yes">Objeto monumental</string>
<string name="poi_industrial_oil">Tipo: industria petrolera</string>
<string name="poi_industrial_wellsite">Tipo: Área de pozos</string>
<string name="poi_industrial_factory">Tipo: fábrica</string>
@ -1868,7 +1868,7 @@
<string name="poi_fuel_100ll">100LL (con plomo, para aviones)</string>
<string name="poi_fuel_autogas">Autogas (Etanol libre de plomo)</string>
<string name="poi_fuel_jeta1">Jet A-1 (diésel)</string>
<string name="poi_fuel_adblue">AdBlue</string>
<string name="poi_fuel_adblue">Líquido de escape de diesel</string>
<string name="poi_fuel_wood">Combustible: madera</string>
<string name="poi_fuel_charcoal">Combustible: carbón vegetal</string>
<string name="poi_fuel_coal">Combustible: carbón</string>
@ -3798,7 +3798,7 @@
<string name="poi_fire_hydrant_type_pipe">Tubo</string>
<string name="poi_drinking_water_refill_network">Red de recarga de agua potable</string>
<string name="poi_drinking_water_refill_no">Recarga de agua potable: no</string>
<string name="poi_drinking_water_refill_yes">Recarga de agua potable: sí</string>
<string name="poi_drinking_water_refill_yes">Sí</string>
<string name="poi_seamark_obstruction">Obstrucción</string>
<string name="poi_seamark_water_level_below_mwl">Nivel de agua: por debajo del nivel medio del agua</string>
<string name="poi_seamark_water_level_above_mwl">Nivel de agua: por encima del nivel medio del agua</string>

View file

@ -23,7 +23,7 @@
<string name="local_openstreetmap_delete">Borrar edición</string>
<string name="local_openstreetmap_descr_title">Edición asíncrona OSM:</string>
<string name="local_openstreetmap_settings">PDI/Notas de OSM guardados en el dispositivo</string>
<string name="local_openstreetmap_settings_descr">Muestra y gestiona PDI/notas de OSM guardadas en la base de datos del dispositivo.</string>
<string name="local_openstreetmap_settings_descr">Muestra y gestiona PDI/Notas de OSM en la base de datos de tu dispositivo.</string>
<string name="live_monitoring_interval_descr">Indica el intervalo del seguimiento en línea.</string>
<string name="live_monitoring_interval">Intervalo del seguimiento en línea</string>
<string name="live_monitoring_url_descr">Indica la dirección web con sintaxis de parámetros : lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}.</string>
@ -163,7 +163,7 @@
<string name="tts_language_not_supported">El idioma elegido es incompatible con el motor TTS (texto a voz) instalado en Android, se usará el idioma TTS predefinido. ¿Buscar otro motor TTS en la tienda de aplicaciones\?</string>
<string name="tts_missing_language_data_title">Faltan datos</string>
<string name="tts_missing_language_data">¿Ir a la tienda de aplicaciones para descargar el idioma elegido?</string>
<string name="gpx_option_reverse_route">Invertir la dirección GPX</string>
<string name="gpx_option_reverse_route">Invertir la dirección de la traza</string>
<string name="gpx_option_destination_point">Usar destino actual</string>
<string name="gpx_option_from_start_point">Pasar a lo largo de la traza completa</string>
<string name="switch_to_vector_map_to_see">Mapa vectorial presente para esta ubicación.
@ -265,10 +265,7 @@
<string name="osmand_net_previously_installed">Todos los datos sin conexión en la versión vieja de OsmAnd son compatibles con la nueva versión, pero los puntos de Favoritos deben exportarse desde la versión vieja y luego, importarse en la nueva.</string>
<string name="build_installed">Compilación {0} instalada ({1}).</string>
<string name="downloading_build">Descargando compilación…</string>
<string name="install_selected_build">¿Instalar OsmAnd\?
\nVersión: {0}
\nFecha: {1}
\nTamaño: {2} MB</string>
<string name="install_selected_build">¿Instalar OsmAnd - {0} de {1} {2} MB\?</string>
<string name="loading_builds_failed">Error al recuperar la lista de compilaciones de OsmAnd</string>
<string name="loading_builds">Cargando compilaciones de OsmAnd…</string>
<string name="select_build_to_install">Instalar compilación de OsmAnd</string>
@ -441,7 +438,7 @@
<string name="search_offline_address">Búsqueda sin conexión</string>
<string name="search_online_address">Búsqueda en línea</string>
<string name="max_level_download_tile">Máximo zoom en línea</string>
<string name="max_level_download_tile_descr">No buscar en las teselas de mapas en línea para niveles de zoom más allá de esto.</string>
<string name="max_level_download_tile_descr">No busca en los mapas en línea niveles de zoom más allá de éste.</string>
<string name="route_general_information">Distancia total %1$s, tiempo de viaje %2$d h %3$d min.</string>
<string name="router_service_descr">Servicios de navegación con o sin conexión.</string>
<string name="router_service">Servicio de navegación</string>
@ -606,7 +603,7 @@
<string name="left_side_navigation_descr">Para países donde la gente conduce por el lado izquierdo del camino.</string>
<string name="unknown_from_location">Punto de partida aún no determinado.</string>
<string name="confirm_interrupt_download">¿Cancelar la descarga\?</string>
<string name="basemap_was_selected_to_download">El mapa base necesario para proporcionar la funcionalidad básica, está en la cola de descarga.</string>
<string name="basemap_was_selected_to_download">El mapa base necesario para proporcionar funcionalidad básica está en la cola de descargas.</string>
<string name="map_online_plugin_is_not_installed">Activa el complemento «Mapas en línea», para elegir diferentes fuentes de mapas</string>
<string name="map_online_data">Mapas en línea y teselas</string>
<string name="map_online_data_descr">Usa mapas en línea (descarga y guarda teselas en la tarjeta de memoria).</string>
@ -729,8 +726,8 @@
<string name="osmand_parking_pm">PM</string>
<string name="osmand_parking_am">AM</string>
<string name="osmand_parking_position_name">Lugar de aparcamiento</string>
<string name="osmand_parking_plugin_description">Este complemento, registra dónde se ha aparcado el automóvil y cuánto tiempo queda (si hay un límite de tiempo).
\nTanto la ubicación como el tiempo del aparcamiento se muestran en el menú principal y en un widget sobre el mapa. Puedes añadir una notificación al calendario, en el caso de que desees tener un recordatorio al respecto.</string>
<string name="osmand_parking_plugin_description">Te permite egistrar dónde has aparcado el automóvil y cuánto tiempo queda (si hay un límite de tiempo).
\nTanto la ubicación como el tiempo del aparcamiento se muestran en el menú principal y en un control sobre el mapa. Puedes añadir un recordatorio al calendario, de Android.</string>
<string name="osmand_parking_plugin_name">Aparcamiento</string>
<string name="context_menu_item_add_parking_point">Marcar como aparcamiento</string>
<string name="context_menu_item_delete_parking_point">Quitar marcador de aparcamiento</string>
@ -890,7 +887,7 @@
<string name="srtm_plugin_name">Curvas de nivel</string>
<string name="download_select_map_types">Otros mapas</string>
<string name="download_srtm_maps">Curvas de nivel</string>
<string name="audionotes_plugin_description">Este complemento, proporciona la funcionalidad para tomar notas de audio, fotografía y/o video durante un viaje, usando un botón en el mapa, o directamente en el menú contextual para cualquier ubicación en el mapa.</string>
<string name="audionotes_plugin_description">Tomar notas de audio/foto/video durante un viaje, usando un botón de mapa o un menú contextual de la ubicación.</string>
<string name="audionotes_plugin_name">Notas audio/vídeo</string>
<string name="osmand_srtm_short_description_80_chars">Complemento OsmAnd para curvas de nivel sin conexión</string>
<string name="osmand_srtm_long_description_1000_chars">Este complemento proporciona una capa superpuesta de curvas de nivel y una capa (de relieve) sombreada, que se pueden visualizar sobre los mapas descargados de OsmAnd. Esta funcionalidad será muy apreciada por atletas, caminantes, excursionistas, y cualquiera interesado en la estructura de relieve de un paisaje.
@ -930,7 +927,7 @@
<string name="recording_description">Grabando %1$s %3$s %2$s</string>
<string name="srtm_paid_version_msg">Por favor, considera pagar por el complemento «Curvas de nivel» para apoyar desarrollos adicionales.</string>
<string name="srtm_paid_version_title">Complemento de curvas de nivel</string>
<string name="dropbox_plugin_description">El complemento de Dropbox, permite sincronizar trazas y notas multimedia con tu cuenta de Dropbox.</string>
<string name="dropbox_plugin_description">Sincroniza trazas y notas multimedia con tu cuenta de Dropbox.</string>
<string name="dropbox_plugin_name">Complemento Dropbox</string>
<string name="intermediate_points_change_order">Cambiar orden</string>
<string name="recording_context_menu_show">Mostrar</string>
@ -1003,8 +1000,8 @@
<string name="plugin_distance_point">Punto</string>
<string name="gpx_file_name">Nombre del archivo GPX</string>
<string name="gpx_saved_sucessfully">Archivo GPX guardado en {0}</string>
<string name="osmand_distance_planning_plugin_description">Este complemento proporciona un widget en el mapa, permitiendo crear caminos pulsando el mapa, usando o modificando archivos GPX existentes, para planificar un viaje y medir la distancia entre puntos. Los resultados pueden guardarse como un archivo GPX y usarse luego para la orientación.</string>
<string name="osmand_distance_planning_plugin_name">Distancias y planificación</string>
<string name="osmand_distance_planning_plugin_description">Crea caminos pulsando en el mapa, o usando o modificando archivos GPX existentes, para planificar un viaje y medir la distancia entre puntos. El resultado puede guardarse como un archivo GPX y usarse luego para la orientación.</string>
<string name="osmand_distance_planning_plugin_name">Calculadora de distancias y herramienta de planificación</string>
<string name="use_distance_measurement_help">* Pulse para marcar un punto.
\n * Mantenga pulsado el mapa para quitar el punto anterior.
\n * Mantenga pulsado en un punto para ver e incluir la descripción.
@ -1309,7 +1306,7 @@
<string name="record_plugin_name">Grabación de viaje</string>
<string name="record_plugin_description">Este complemento activa la funcionalidad para registrar y guardar tus trazas manualmente pulsando el widget de grabación GPX en el mapa, o automáticamente registrando todas tus rutas navegadas en un archivo GPX.
\n
\nLas trazas grabadas pueden ser compartidas con sus amigos o ser usadas para contribuir a OSM. Los atletas pueden usar las trazas grabadas para seguir sus entrenamientos. Algunos análisis básicos de trazas se pueden realizar directamente en OsmAnd, como tiempos por vuelta, velocidad media, etc., y por supuesto las trazas pueden analizarse posteriormente con herramientas de análisis de terceros.</string>
\nLas trazas grabadas pueden ser compartidas con tus amigos o ser usadas para contribuir a OSM. Los atletas pueden usar las trazas grabadas para monitorizar sus entrenamientos. Algunos análisis básicos de trazas pueden realizarse directamente en OsmAnd, como tiempos por vuelta, velocidad media, etc., y por supuesto las trazas pueden analizarse posteriormente con herramientas de análisis de terceros.</string>
<string name="rendering_attr_publicTransportMode_name">Rutas de autobús, trolebús y lanzadera</string>
<string name="save_track_interval_globally">Intervalo de registro</string>
<string name="save_track_to_gpx_globally_descr">Registra la ubicación en un archivo GPX, pudiendo des/activarlo usando el widget de grabación GPX en el mapa.</string>
@ -1433,7 +1430,7 @@
<string name="rendering_attr_pisteGrooming_name">Pista de entrenamiento</string>
<string name="roads_only">Sólo caminos</string>
<string name="share_note">Compartir nota</string>
<string name="notes">Notas</string>
<string name="notes">Notas A/V</string>
<string name="online_map">Mapa en línea</string>
<string name="shared_string_export">Exportar</string>
<string name="shared_string_audio">Audio</string>
@ -1781,7 +1778,7 @@
<string name="dashboard_or_drawer_description">Se ofrece la opción de controlar la aplicación principalmente a través del panel de control flexible o de un menú estático. Se puede cambiar esto luego, en los ajustes del panel.</string>
<string name="use_dashboard_btn">Usar panel de control</string>
<string name="use_drawer_btn">Usar menú</string>
<string name="access_from_map_description">El botón del menú, muestra el panel de control en lugar del menú</string>
<string name="access_from_map_description">El botón del menú muestra el panel de control en lugar del menú</string>
<string name="access_from_map">Acceso desde el mapa</string>
<string name="please_specify_poi_type_only_from_list">Indica el tipo de PDI correcto u omítelo.</string>
<string name="routing_attr_avoid_stairs_name">Sin escaleras</string>
@ -1880,7 +1877,7 @@
\nSe utiliza {3} MB temporalmente, {1} MB constantemente. (De {2} MB.)</string>
<string name="select_map_marker">Elegir marcador del mapa</string>
<string name="map_markers_other">Otros marcadores</string>
<string name="upload_osm_note_description">Subir notas de OSM anónimas o usar el perfil de OpenStreetMap.org.</string>
<string name="upload_osm_note_description">Sube tu nota de OSM de forma anónima o usando tu perfil de OpenStreetMap.org.</string>
<string name="upload_osm_note">Subir nota(s) de OSM</string>
<string name="upload_anonymously">Subir anónimamente</string>
<string name="shared_string_topbar">Barra superior</string>
@ -1910,7 +1907,7 @@
\nParte de los ingresos vuelven a la comunidad de OSM y se paga por cada contribución OSM.
\nSi amas a OsmAnd, OSM y quieres apoyarlos y ser apoyado por ellos, esta es una perfecta manera de hacerlo.</string>
<string name="show_transparency_seekbar">Mostrar barra de transparencia en el mapa</string>
<string name="storage_directory_readonly_desc">Se ha cambiado a la memoria interna, porque la carpeta de almacenamiento de datos elegida es de sólo lectura. Elige un directorio de almacenamiento válido.</string>
<string name="storage_directory_readonly_desc">Se ha cambiado a la memoria interna, porque la carpeta de almacenamiento de datos elegida está protegida de escritura. Elige un directorio de almacenamiento válido.</string>
<string name="storage_directory_shared">Memoria compartida</string>
<string name="storage_permission_restart_is_required">La aplicación ya permite escribir en el almacenamiento externo, pero se debe reiniciar la aplicación.</string>
<string name="shared_string_move_up">Subir ↑</string>
@ -1918,7 +1915,7 @@
<string name="finish_navigation">Finalizar navegación</string>
<string name="avoid_road">Evitar camino</string>
<string name="full_report">Informe completo</string>
<string name="open_street_map_login_and_pass">Nombre de usuario y contraseña de OpenStreetMap</string>
<string name="open_street_map_login_and_pass">Nombre de usuario y contraseña de OSM</string>
<string name="report">Informe</string>
<string name="no_map_markers_found">Añade marcadores a través del mapa</string>
<string name="no_waypoints_found">No se encontraron puntos de referencia</string>
@ -2009,16 +2006,16 @@
<string name="no_inet_connection_desc_map">Necesario para descargar mapas.</string>
<string name="search_location">Buscando la ubicación…</string>
<string name="storage_free_space">Espacio libre</string>
<string name="storage_place_description">Almacenamiento de datos de OsmAnd (para mapas, archivos GPX, etc.): %1$s.</string>
<string name="storage_place_description">Almacenamiento de datos de OsmAnd (para mapas, trazas, etc.): %1$s.</string>
<string name="give_permission">Conceder permiso</string>
<string name="allow_access_location">Permitir el acceso a la ubicación</string>
<string name="first_usage_greeting">Obtenga direcciones y descubra lugares nuevos, sin una conexión a Internet</string>
<string name="search_my_location">Encontrar mi ubicación</string>
<string name="no_update_info_desc">Omite la búsqueda de nuevas versiones o descuentos relacionados con OsmAnd.</string>
<string name="no_update_info">Ocultar nuevas versiones</string>
<string name="osm_live_payment_desc">Suscripción mensual. Puede cancelarlo en cualquier momento en Google Play.</string>
<string name="donation_to_osm">Donaciones a la comunidad de OpenStreetMap</string>
<string name="donation_to_osm_desc">Parte de tu donación se envía a usuarios que realicen cambios en OpenStreetMap. El costo de la suscripción sigue siendo la misma.</string>
<string name="osm_live_payment_desc">Suscripción mensual. Puedes cancelarla en cualquier momento en Google Play.</string>
<string name="donation_to_osm">Donación a la comunidad de OSM</string>
<string name="donation_to_osm_desc">Parte de tu donación se envía a los contribuidores a OSM. El coste de la suscripción sigue siendo el mismo.</string>
<string name="osm_live_subscription_desc">La suscripción permite actualizaciones cada hora, día o semana y descargas ilimitadas para los mapas de todo el mundo.</string>
<string name="get_it">Obtener</string>
<string name="get_for">Obtener por %1$s</string>
@ -2032,8 +2029,8 @@
<string name="upload_poi">Subir PDI</string>
<string name="route_calculation">Cálculo de la ruta</string>
<string name="search_map_hint">Ciudad o región</string>
<string name="gpx_no_tracks_title">Sin archivos GPX aún</string>
<string name="gpx_no_tracks_title_folder">También puedes añadir archivos GPX a la carpeta</string>
<string name="gpx_no_tracks_title">No tienes archivos de trazas aún</string>
<string name="gpx_no_tracks_title_folder">También puedes añadir archivos de trazas a la carpeta</string>
<string name="gpx_add_track">Añadir más…</string>
<string name="shared_string_appearance">Aspecto</string>
<string name="shared_string_notifications">Notificaciones</string>
@ -2047,7 +2044,7 @@
<string name="routing_attr_allow_motorway_name">Usar autopistas</string>
<string name="routing_attr_allow_motorway_description">Permite usar autopistas.</string>
<string name="trip_rec_notification_settings">Activar la grabación rápida</string>
<string name="trip_rec_notification_settings_desc">Muestra una notificación del sistema que permite la grabación del viaje.</string>
<string name="trip_rec_notification_settings_desc">Muestra una notificación del sistema que permite empezar la grabación del viaje.</string>
<string name="shared_string_trip">Viaje</string>
<string name="shared_string_recorded">Grabado</string>
<string name="shared_string_record">Grabar</string>
@ -2121,7 +2118,7 @@
<string name="quick_action_take_photo_note_descr">Un botón que añade una nota fotográfica en el centro de la pantalla.</string>
<string name="quick_action_add_osm_bug_descr">Un botón que añade una nota de OSM en el centro de la pantalla.</string>
<string name="quick_action_add_poi_descr">Un botón que añade un PDI en el centro de la pantalla.</string>
<string name="quick_action_navigation_voice_descr">Un botón que des/activa las indicaciones por voz durante la navegación.</string>
<string name="quick_action_navigation_voice_descr">Un botón que activa o desactiva las indicaciones por voz durante la navegación.</string>
<string name="quick_action_add_parking_descr">Un botón que añade la ubicación del aparcamiento en el centro de la pantalla.</string>
<string name="quick_action_interim_dialog">Mostrar un diálogo temporal</string>
<string name="favorite_autofill_toast_text">" guardado como "</string>
@ -2188,7 +2185,7 @@
\nProporciona un código completo</string>
<string name="navigate_point_olc_info_area">OLC completo y válido.
\nÁrea representada: %1$s x %2$s</string>
<string name="quick_action_page_list_descr">Un botón que muestra la siguiente lista.</string>
<string name="quick_action_page_list_descr">Un botón para paginar a través de la lista de abajo.</string>
<string name="quick_action_map_overlay_switch">Mapa superpuesto cambiado a «%s».</string>
<string name="quick_action_map_underlay_switch">Mapa subyacente cambiado a «%s».</string>
<string name="shared_string_slope">Pendiente</string>
@ -2286,15 +2283,15 @@
\n ¡Más países alrededor del globo están disponibles para descargar!
\n Obtén un navegador confiable en tu país - ya sea Francia, Alemania, México, Reino Unido, España, Países bajos, Estados Unidos, Rusia, Brasil o cualquier otro.</string>
<string name="quick_action_auto_zoom">Alternar zoom automático del mapa</string>
<string name="quick_action_auto_zoom_desc">Un botón que des/activa el zoom automático del mapa de acuerdo a la velocidad.</string>
<string name="quick_action_auto_zoom_on">Activar zoom automático del mapa</string>
<string name="quick_action_auto_zoom_off">Desactivar zoom automático del mapa</string>
<string name="quick_action_auto_zoom_desc">Botón para activar o desactivar el zoom automático controlado por la velocidad.</string>
<string name="quick_action_auto_zoom_on">Activar zoom automático</string>
<string name="quick_action_auto_zoom_off">Desactivar zoom automático</string>
<string name="quick_action_add_destination">Definir destino</string>
<string name="quick_action_replace_destination">Reemplazar destino</string>
<string name="quick_action_add_first_intermediate">Añadir primer destino intermedio</string>
<string name="quick_action_add_destination_desc">Un botón que añade el destino de la ruta en el centro de la pantalla, cualquier destino previamente elegido se convierte en el último destino intermedio.</string>
<string name="quick_action_replace_destination_desc">Este botón de acción, añade un nuevo destino de ruta en el centro de la pantalla, reemplazando el anterior destino (si existe).</string>
<string name="quick_action_add_first_intermediate_desc">Un botón que añade el primer destino intermedio en el centro de la pantalla.</string>
<string name="quick_action_add_destination_desc">Un botón que añade el centro de la pantalla como destino de la ruta, cualquier destino previamente elegido se convierte en el último destino intermedio.</string>
<string name="quick_action_replace_destination_desc">Un botón que añade el centro de la pantalla como destino de la nueva ruta, reemplazando el anterior destino (si existe).</string>
<string name="quick_action_add_first_intermediate_desc">Un botón que añade el centro de la pantalla como el primer destino intermedio.</string>
<string name="no_overlay">Sin superposición</string>
<string name="no_underlay">Sin subyacencia</string>
<string name="subscribe_email_error">Error</string>
@ -2327,13 +2324,14 @@
<string name="animate_my_location">Ubicación propia animada</string>
<string name="animate_my_location_desc">Activa el desplazamiento animado del mapa para «Mi ubicación» durante la navegación.</string>
<string name="shared_string_overview">Resumen</string>
<string name="osmand_plus_extended_description_part2">Navegación GPS
<string name="osmand_plus_extended_description_part2">Navegación
\n • Funciona en línea (rápido) o sin conexión (sin cargos de roaming al viajar al extranjero)
\n • Guía por voz giro-a-giro (voces grabadas y sintetizadas)
\n • (Opcional) Guía de carriles, nombres de calles y tiempo estimado al destino
\n • Soporta puntos intermedios en el itinerario
\n • (Opcional) Guía de carriles, nombres de calles y tiempo estimado de llegada
\n • Soporta puntos intermedios en tu itinerario
\n • La ruta se recalcula al salirse de la misma
\n • Busca destinos por dirección, por tipo (por ejemplo: Restaurantes, hoteles, gasolineras, museos), o por coordenada geográfica</string>
\n • Busca lugares por dirección, por tipo (por ejemplo: Restaurantes, hoteles, gasolineras, museos), o por coordenadas geográficas
\n</string>
<string name="osmand_plus_extended_description_part3">Vista del mapa
\n • Muestra tu ubicación y orientación
\n • (Opcional) Ajusta el mapa a la dirección del movimiento (o la brújula)
@ -2397,7 +2395,7 @@
<string name="quick_action_showhide_osmbugs_title">Mostrar u ocultar notas de OSM</string>
<string name="quick_action_osmbugs_show">Mostrar notas de OSM</string>
<string name="quick_action_osmbugs_hide">Ocultar notas de OSM</string>
<string name="quick_action_showhide_osmbugs_descr">Un botón que muestra u oculta las notas de OSM en el mapa.</string>
<string name="quick_action_showhide_osmbugs_descr">Botón para mostrar u ocultar las notas de OSM en el mapa.</string>
<string name="sorted_by_distance">Ordenados por distancia</string>
<string name="search_favorites">Buscar en Favoritos</string>
<string name="hide_from_zoom_level">Ocultar desde el nivel de zoom</string>
@ -2419,7 +2417,7 @@
<string name="open_mapillary">Abrir Mapillary</string>
<string name="shared_string_install">Instalar</string>
<string name="improve_coverage_mapillary">Mejorar cobertura de fotos con Mapillary</string>
<string name="improve_coverage_install_mapillary_desc">Instala Mapillary para añadir una o más fotos a esta ubicación del mapa.</string>
<string name="improve_coverage_install_mapillary_desc">Instala Mapillary para añadir fotos a esta ubicación del mapa.</string>
<string name="online_photos">Fotos en línea</string>
<string name="mapillary_image">Imagen de Mapillary</string>
<string name="shared_string_permissions">Permisos</string>
@ -2449,10 +2447,10 @@
<string name="min_max">Min/Máx</string>
<string name="rendering_value_translucent_pink_name">Rosa translúcido</string>
<string name="quick_action_resume_pause_navigation">Pausar/reanudar navegación</string>
<string name="quick_action_resume_pause_navigation_descr">Este botón de acción, pausa o reanuda la navegación.</string>
<string name="quick_action_resume_pause_navigation_descr">Botón para pausar o reanudar navegación.</string>
<string name="quick_action_show_navigation_finish_dialog">Mostrar diálogo «Navegación finalizada»</string>
<string name="quick_action_start_stop_navigation">Iniciar/parar navegación</string>
<string name="quick_action_start_stop_navigation_descr">Este botón de acción, inicia o para la navegación.</string>
<string name="quick_action_start_stop_navigation_descr">Botón para iniciar o terminar la navegación.</string>
<string name="live_monitoring_max_interval_to_send">Tiempo del búfer para el seguimiento en línea</string>
<string name="live_monitoring_max_interval_to_send_desrc">Indica el tiempo que el búfer mantendrá los lugares para enviar sin conexión</string>
<string name="none_point_error">Añadir al menos un punto.</string>
@ -2468,7 +2466,7 @@
<string name="route_point_one">Punto de ruta 1</string>
<string name="waypoint_one">Punto de referencia 1</string>
<string name="do_not_use_animations">Sin animaciones</string>
<string name="do_not_use_animations_descr">Desactiva las animaciones en la aplicación.</string>
<string name="do_not_use_animations_descr">Desactiva las animaciones de los mapas.</string>
<string name="keep_showing_on_map">Mantener en el mapa</string>
<string name="exit_without_saving">¿Salir sin guardar?</string>
<string name="line">Línea</string>
@ -2492,9 +2490,9 @@
<string name="move_all_to_history">Mover todo al historial</string>
<string name="show_direction">Indicación de distancia</string>
<string name="sort_by">Ordenar por</string>
<string name="marker_show_distance_descr">Elige cómo se indica la distancia y dirección a los marcadores del mapa en la pantalla del mapa:</string>
<string name="marker_show_distance_descr">Elige cómo se indica la distancia y dirección a los marcadores del mapa en el mapa:</string>
<string name="map_orientation_change_in_accordance_with_speed">Umbral de orientación del mapa</string>
<string name="map_orientation_change_in_accordance_with_speed_descr">Velocidad a partir de la cual la orientación del mapa cambia de «Dirección del movimiento» a «Dirección de la brújula».</string>
<string name="map_orientation_change_in_accordance_with_speed_descr">Selecciona la velocidad a partir de la que la orientación del mapa cambia de «Dirección del movimiento» a «Dirección de la brújula».</string>
<string name="all_markers_moved_to_history">Todos los marcadores del mapa movidos al historial</string>
<string name="marker_moved_to_history">Marcador del mapa movido al historial</string>
<string name="marker_moved_to_active">Marcador del mapa movido a los activos</string>
@ -2603,8 +2601,8 @@
<string name="one_tap_active_descr">Pulsa un marcador en el mapa para moverlo al primer lugar de los marcadores activos, sin abrir el menú contextual.</string>
<string name="one_tap_active">Activar «Una pulsación»</string>
<string name="empty_state_av_notes">¡Toma notas!</string>
<string name="empty_state_av_notes_desc">Añade una nota de audio, vídeo o foto para cada punto del mapa, utilizando el widget o el menú contextual.</string>
<string name="notes_by_date">Notas por fecha</string>
<string name="empty_state_av_notes_desc">Añade una nota de audio, vídeo o foto a cualquier punto del mapa, utilizando el control o el menú contextual.</string>
<string name="notes_by_date">Notas A/V por fecha</string>
<string name="by_date">Por fecha</string>
<string name="by_type">Por tipo</string>
<string name="what_is_here">Aquí hay:</string>
@ -2852,7 +2850,7 @@
<string name="lang_gn_py">Guaraní</string>
<string name="run_full_osmand_msg">Está utilizando el mapa «{0}» que funciona con OsmAnd. ¿Quiere ejecutar la versión completa de OsmAnd\?</string>
<string name="run_full_osmand_header">¿Ejecutar OsmAnd\?</string>
<string name="quick_action_switch_day_night_descr">Un botón que alterna entre el modo diurno y nocturno para OsmAnd.</string>
<string name="quick_action_switch_day_night_descr">Un botón que alterna entre los modos diurno y nocturno para OsmAnd.</string>
<string name="quick_action_switch_day_mode">Modo diurno</string>
<string name="quick_action_switch_night_mode">Modo nocturno</string>
<string name="quick_action_day_night_switch_mode">Alternar modos diurno/nocturno</string>
@ -2956,7 +2954,7 @@
<string name="rendering_attr_highway_class_motorway_name">Autopista</string>
<string name="rendering_attr_highway_class_state_road_name">Carretera/ruta estatal</string>
<string name="rendering_attr_highway_class_road_name">Carretera principal</string>
<string name="rendering_attr_highway_class_street_name">Calle residencial</string>
<string name="rendering_attr_highway_class_street_name">Calle</string>
<string name="rendering_attr_highway_class_service_name">Vía de servicio</string>
<string name="rendering_attr_highway_class_footway_name">Acera</string>
<string name="rendering_attr_highway_class_track_name">Camino rural</string>
@ -3103,7 +3101,7 @@
<string name="select_nav_profile_dialog_title">Elegir el tipo de navegación</string>
<string name="base_profile_descr_car">Automóvil, camión, motocicleta</string>
<string name="base_profile_descr_bicycle">Bicicleta de montaña, ciclomotor, caballo</string>
<string name="base_profile_descr_pedestrian">Caminata, senderismo, correr</string>
<string name="base_profile_descr_pedestrian">Caminata, senderismo, carrera</string>
<string name="base_profile_descr_public_transport">Tipos de transporte público</string>
<string name="base_profile_descr_boat">Barco, remo, vela</string>
<string name="base_profile_descr_aircraft">Avión, ala delta</string>
@ -3139,7 +3137,7 @@
<string name="routing_attr_difficulty_preference_name">Dificultad preferida</string>
<string name="routing_attr_difficulty_preference_description">Preferir rutas de esta dificultad, aunque el trazado sobre pistas más duras o más fáciles sigue siendo posible si son más cortas.</string>
<string name="routing_attr_freeride_policy_name">Fuera de pista</string>
<string name="routing_attr_freeride_policy_description">Los senderos libres y fuera de pista son rutas y pasajes no oficiales. Típicamente descuidados, no mantenidos por los oficiales y no controlados por la noche. Entrar bajo su propio riesgo.</string>
<string name="routing_attr_freeride_policy_description">Los senderos libres y fuera de pista son rutas y pasajes no oficiales. Típicamente descuidados, no mantenidos y no controlados por la noche. Entra bajo tu propio riesgo.</string>
<string name="routing_profile_geocoding">Geocodificación</string>
<string name="shared_string_crash">Error</string>
<string name="app_mode_offroad">Todo terreno</string>
@ -3361,7 +3359,7 @@
<string name="empty_filename">El nombre de archivo está vacío</string>
<string name="track_saved">Traza guardada</string>
<string name="shared_string_revert">Revertir</string>
<string name="quick_action_directions_from_desc">Un botón para centrar en la pantalla el punto de partida y calcular la ruta hacia el destino o abre un cuadro de diálogo para elegir el destino si el marcador no está en el mapa.</string>
<string name="quick_action_directions_from_desc">Un botón que añade el centro de la pantalla como punto de partida. Pedirá luego que se fije el destino o iniciará el cálculo de la ruta.</string>
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Mostrar nodo de la red de rutas ciclistas</string>
<string name="clear_confirmation_msg">¿Borrar %1$s\?</string>
<string name="download_map_dialog">Diálogo de descarga del mapa</string>
@ -3394,7 +3392,7 @@
<string name="select_color">Elegir el color</string>
<string name="edit_profiles_descr">Los perfiles predefinidos de OsmAnd no pueden borrarse , pero sí desactivarse (en la pantalla anterior), o moverse a la parte inferior.</string>
<string name="edit_profiles">Editar perfiles</string>
<string name="select_nav_profile_dialog_message">El \'tipo de navegación\' domina como se calculan las ruta.</string>
<string name="select_nav_profile_dialog_message">El \'Tipo de navegación\' determina cómo se calculan las rutas.</string>
<string name="profile_appearance">Apariencia del perfil</string>
<string name="choose_icon_color_name">Icono, color y nombre</string>
<string name="reorder_profiles">Editar lista de perfiles</string>
@ -3679,7 +3677,7 @@
<string name="search_poi_types">Buscar tipos de PDI</string>
<string name="download_unsupported_action">Acción %1$s no admitida</string>
<string name="index_item_world_basemap_detailed">Mapa general del mundo (detallado)</string>
<string name="unsupported_type_error">Tipo no admitido</string>
<string name="unsupported_type_error">Tipo no soportado</string>
<string name="width_limit_description">Proporciona la anchura de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos anchos.</string>
<string name="height_limit_description">Proporciona la altura de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos altos.</string>
<string name="weight_limit_description">Proporciona el peso de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos pesados.</string>
@ -3715,7 +3713,7 @@
<string name="tiles_storage_descr">Elige cómo se guardarán las teselas descargadas.</string>
<string name="export_import_quick_actions_with_profiles_promo">Puede exportar o importar acciones rápidas con perfiles de aplicación.</string>
<string name="shared_string_delete_all_q">¿Eliminar todo\?</string>
<string name="delete_all_actions_message_q">¿Estás seguro de que deseas eliminar irrevocablemente% d acciones rápidas\?</string>
<string name="delete_all_actions_message_q">¿Estás seguro deseas eliminar de forma irreversible %d acciones rápidas\?</string>
<string name="screen_timeout">Tiempo de apagado de la pantalla</string>
<string name="screen_timeout_descr">Si \"%1$s\" está encendido, el tiempo de actividad dependerá de ello.</string>
<string name="shared_string_meters">metros</string>
@ -3740,7 +3738,7 @@
<string name="lang_ur">Urdu</string>
<string name="lang_tg">Tayiko</string>
<string name="lang_bar">Bávaro</string>
<string name="tracker_item">Rastreador OsmAnd</string>
<string name="tracker_item">Trazador OsmAnd</string>
<string name="legend_item_description">La guía para la simbología del mapa.</string>
<string name="parking_positions">Posiciones de estacionamiento</string>
<string name="turn_screen_on_power_button_disabled">Deshabilitado. Requiere \'Mantener la pantalla encendida\' dentro de \'Tiempo de espera después de la activación\'.</string>
@ -3849,7 +3847,7 @@
<string name="threshold_distance">Distancia umbral</string>
<string name="navigation_profile">Perfil de navegación</string>
<string name="route_between_points_add_track_desc">Selecciona un archivo de traza al que agregar el nuevo segmento.</string>
<string name="street_level_imagery">Imágenes a pie de calle</string>
<string name="street_level_imagery">Fotos a pie de calle</string>
<string name="plan_route_exit_dialog_descr">¿Estás seguro de que quieres descartar todos los cambios en la ruta planeada cerrándola\?</string>
<string name="in_case_of_reverse_direction">En caso de dirección contraria</string>
<string name="shared_string_save_as_gpx">Guardar como nuevo archivo de traza</string>

View file

@ -524,7 +524,7 @@
<string name="poi_fuel_octane_95">Oktaan 95</string>
<string name="poi_fuel_octane_98">Oktaan 98</string>
<string name="poi_fuel_octane_100">Oktaan 100</string>
<string name="poi_fuel_cng">CNG</string>
<string name="poi_fuel_cng">Surugaas</string>
<string name="poi_fuel_1_25">1:25 kütus</string>
<string name="poi_fuel_1_50">1:50 kütus</string>
<string name="poi_fuel_ethanol">Etanool</string>
@ -3826,4 +3826,5 @@
<string name="poi_recycling_small_electrical_appliances">Väikesed elektriseadmed</string>
<string name="poi_beehive">Mesitaru</string>
<string name="poi_nuts">Pähklipood</string>
<string name="poi_fuel_lng">Veeldatud maagaas</string>
</resources>

View file

@ -3497,7 +3497,7 @@
<string name="custom_color">Kohandatud värv</string>
<string name="set_working_days_to_continue">Jätkamiseks vali tööpäevad</string>
<string name="route_between_points">Teekond punktide vahel</string>
<string name="plan_a_route">Kavanda teekonda</string>
<string name="plan_a_route">Kavanda teekond</string>
<string name="add_to_a_track">Lisa rajale</string>
<string name="track_show_start_finish_icons">Näita alguse ja lõpu ikoone</string>
<string name="select_track_width">Vali laius</string>
@ -3762,4 +3762,6 @@
<string name="sort_last_modified">Viimati muudetud</string>
<string name="sort_name_descending">Nimi: Z A</string>
<string name="sort_name_ascending">Nimi: A Z</string>
<string name="screen_timeout">Ekraani väljalülitamine</string>
<string name="app_mode_wheelchair_forward">Ratastool edasi</string>
</resources>

View file

@ -9,7 +9,7 @@
<string name="poi_butcher">فروشگاه گوشت</string>
<string name="poi_deli">بقالی</string>
<string name="poi_farm">فروشگاه محصولات دامی</string>
<string name="poi_greengrocer">سبزی فروشی</string>
<string name="poi_greengrocer">میوه و سبزی‌فروشی</string>
<string name="poi_seafood">فروشگاه غذاهای دریایی</string>
<string name="poi_confectionery">شیرینی و آجیل فروشی</string>
<string name="poi_ice_cream">بستنی فروشی</string>

View file

@ -3278,7 +3278,7 @@
<string name="routing_attr_piste_type_skitour_name">تور اسکی</string>
<string name="routing_attr_piste_type_skitour_description">مسیرها برای تور اسکی.</string>
<string name="app_mode_camper">کمپر</string>
<string name="app_mode_campervan">ون کمپر</string>
<string name="app_mode_campervan">ون کمپر (RV)</string>
<string name="app_mode_wagon">واگن</string>
<string name="app_mode_pickup_truck">کامیون پیک‌آپ</string>
<string name="analytics_pref_title">تحلیل‌ها</string>
@ -3553,7 +3553,7 @@
<string name="profile_type_custom_string">پروفایل سفارشی</string>
<string name="shared_string_angle_param">زاویه: %s°</string>
<string name="shared_string_angle">زاویه</string>
<string name="recalc_angle_dialog_descr">تا مسیریابی مجدد انجام شود، از موقعیت من تا مسیر محاسبه‌شده پاره‌خط مستقیمی نمایش داده می‌شود</string>
<string name="recalc_angle_dialog_descr">تا مسیریابی مجدد انجام نشده، از موقعیت من تا مسیر محاسبه‌شده پاره‌خط مستقیمی نمایش داده می‌شود</string>
<string name="recalc_angle_dialog_title">کمترین زاویه میان موقعیت من و مسیر</string>
<string name="shared_string_preparing">آماده‌سازی</string>
<string name="shared_string_nothing_selected">چیزی انتخاب نشده</string>
@ -3873,7 +3873,7 @@
<string name="save_as_new_track">ذخیره به‌عنوان رد جدید</string>
<string name="reverse_route">برعکس‌کردن مسیر</string>
<string name="route_between_points_whole_track_button_desc">تمام رد با استفاده از پروفایل انتخابی بازمحاسبه خواهد شد.</string>
<string name="route_between_points_next_segment_button_desc">با استفاده از پروفایل انتخابی فقط پارهٔ بعدی بازمحاسبه خواهد شد.</string>
<string name="route_between_points_next_segment_button_desc">فقط پارهٔ بعدی با استفاده از پروفایل انتخابی بازمحاسبه خواهد شد.</string>
<string name="all_next_segments">همهٔ پاره‌های بعدی</string>
<string name="previous_segment">پارهٔ قبلی</string>
<string name="all_previous_segments">همهٔ پاره‌های قبلی</string>
@ -3928,4 +3928,9 @@
<string name="save_global_track_interval_descr">بازهٔ زمانی برای ضبط رد را انتخاب کنید (که از طریق ابزار ضبط سفر روی نقشه فعال می‌شود).</string>
<string name="gpx_monitoring_stop">نگه‌داشتن ضبط سفر</string>
<string name="gpx_monitoring_start">ازسرگیری ضبط سفر</string>
<string name="app_mode_inline_skates">اسکیت این‌لاین</string>
<string name="app_mode_enduro_motorcycle">موتور پرشی</string>
<string name="app_mode_motor_scooter">اسکوتر موتوری</string>
<string name="app_mode_wheelchair_forward">ویلچر رو به جلو</string>
<string name="threshold_distance">فاصله آستانه</string>
</resources>

View file

@ -1762,7 +1762,7 @@
<string name="poi_trade_plumbing">Fournitures de plomberie</string>
<string name="poi_trade_wood">Fournitures de bois</string>
<string name="poi_bicycle_parking_anchors">Ancres pour vélo</string>
<string name="poi_bicycle_parking_stands">Râtelier pour vélo</string>
<string name="poi_bicycle_parking_stands">Arceaux pour vélo</string>
<string name="poi_information_terminal">Terminal d\'informations</string>
<string name="poi_information_tactile_map">Carte tactile</string>
<string name="poi_board_type_notice">Tableau d\'affichage</string>
@ -3380,7 +3380,7 @@
<string name="poi_zoo_wildlife_park">Parc animalier</string>
<string name="poi_zoo_enclosure">Enceinte</string>
<string name="poi_zoo_safari_park">Parc safari</string>
<string name="poi_stands">Râtelier pour vélo</string>
<string name="poi_stands">Arceaux pour vélo</string>
<string name="poi_motorcycle_type_sportbike">Vélo de sport</string>
<string name="poi_motorcycle_type_chopper">Hachoir</string>
<string name="poi_motorcycle_type_offroad">Hors route</string>

View file

@ -1954,7 +1954,7 @@
<string name="use_osm_live_routing">Navigazione OsmAnd Live</string>
<string name="map_widget_battery">Livello della batteria</string>
<string name="lang_hu_formal">Ungherese (formale)</string>
<string name="current_track">Tracciato attuale</string>
<string name="current_track">Traccia attuale</string>
<string name="change_markers_position">Cambia posizione del marcatore</string>
<string name="lang_es_us">Spagnolo americano</string>
<string name="lang_ast">Asturiano</string>
@ -3620,7 +3620,7 @@
<string name="index_item_world_basemap_detailed">Mappa mondiale generale (dettagliata)</string>
<string name="extra_maps_menu_group">Mappe extra</string>
<string name="download_unsupported_action">Azione non supportata %1$s</string>
<string name="tracker_item">OsmAnd tracker</string>
<string name="tracker_item">Tracker OsmAnd</string>
<string name="mapillary_item">OsmAnd + Mapillary</string>
<string name="quick_action_item">Azione veloce</string>
<string name="radius_ruler_item">Righello radiale</string>
@ -3900,7 +3900,8 @@
\n
\n</string>
<string name="simplified_track">Traccia semplificata</string>
<string name="sort_last_modified">Ultimo modificato</string>
<string name="sort_last_modified">Cronologico</string>
<string name="sort_name_descending">Nome: Z A</string>
<string name="sort_name_ascending">Nome: A Z</string>
<string name="start_finish_icons">Icona Partenza/Arrivo</string>
</resources>

View file

@ -3909,4 +3909,5 @@
<string name="sort_last_modified">שינוי אחרון</string>
<string name="sort_name_descending">שם: ת א</string>
<string name="sort_name_ascending">שם: א ת</string>
<string name="start_finish_icons">סמלי התחלה/סיום</string>
</resources>

View file

@ -1258,7 +1258,7 @@
<string name="poi_vacuum_cleaner_yes">有り</string>
<string name="poi_vacuum_cleaner_no">掃除機:無し</string>
<string name="poi_amenity_vacuum_cleaner">掃除機</string>
<string name="poi_fuel_adblue">アドブルー・尿素水還元剤</string>
<string name="poi_fuel_adblue">ディーゼル排気用液(AdBlue・尿素水)</string>
<string name="poi_drive_through">ドライブスルー</string>
<string name="poi_drive_through_yes">有り</string>
<string name="poi_drive_through_no">無し</string>
@ -3822,4 +3822,16 @@
<string name="poi_osmand_fire_hydrant_pressure_suction">吸引</string>
<string name="poi_osmand_fire_hydrant_pressure_pressurized">加圧</string>
<string name="poi_fire_hydrant_style_water_source_groundwater">地下水</string>
<string name="poi_nuts">ナッツ専門店</string>
<string name="poi_beehive">養蜂箱</string>
<string name="poi_departures_board_realtime">リアルタイム時刻表</string>
<string name="poi_departures_board_timetable">一般的な時刻表</string>
<string name="poi_departures_board_delay">大まかな時刻表</string>
<string name="poi_departures_board_yes">有り</string>
<string name="poi_departures_board_no">時刻表:無し</string>
<string name="poi_elevator">エレベーター</string>
<string name="poi_city_block">街区</string>
<string name="poi_borough">行政区</string>
<string name="poi_give_box">ギブボックス(提供品置場)</string>
<string name="poi_fire_hydrant_type_pipe">簡易給水栓</string>
</resources>

View file

@ -1190,7 +1190,7 @@ POIの更新は利用できません</string>
<string name="gpx_info_start_time">"出発時刻: %1$tF, %1$tT "</string>
<string name="gpx_info_end_time">"到着時刻: %1$tF, %1$tT "</string>
<string name="gpx_info_average_speed">"平均速度: %1$s "</string>
<string name="gpx_info_maximum_speed">"最高速度: %1$s "</string>
<string name="gpx_info_maximum_speed">最高速度: %1$s</string>
<string name="gpx_info_avg_altitude">平均標高: %1$s</string>
<string name="gpx_info_diff_altitude">標高差: %1$s</string>
<string name="gpx_info_asc_altitude">上り/下り: %1$s</string>
@ -1350,7 +1350,7 @@ POIの更新は利用できません</string>
<string name="wake_on_voice">画面の電源オン設定</string>
<string name="wake_on_voice_descr">方向転換地点に近づいたらデバイスの画面を(オフの場合指定時間)オンにします</string>
<string name="shared_string_never">しない</string>
<string name="impassable_road">除外する道の指定…</string>
<string name="impassable_road">避ける道の指定…</string>
<string name="rendering_attr_trainLightrailRoutes_name">電車でのルート</string>
<string name="rendering_attr_tramRoutes_name">路面電車でのルート</string>
<string name="rendering_attr_shareTaxiRoutes_name">タクシーのルート共有</string>
@ -1816,7 +1816,7 @@ POIの更新は利用できません</string>
<string name="shared_string_move_up">上に移動</string>
<string name="shared_string_move_down">下に移動</string>
<string name="finish_navigation">ナビゲーションの終了</string>
<string name="avoid_road">使用しない道路として指定</string>
<string name="avoid_road">避ける道を指定</string>
<string name="storage_directory_readonly_desc">選択したデータ保存フォルダーが書き込み保護されているため、内部メモリに切り替えました。書き込み可能な保存用ディレクトリを選択してください。</string>
<string name="storage_directory_shared">共有記憶域</string>
<string name="full_report">より詳細なレポートは以下サイトにて</string>
@ -3010,9 +3010,9 @@ POIの更新は利用できません</string>
<string name="select_icon_profile_dialog_title">アイコン選択</string>
<string name="profile_type_base_string">基本プロファイル</string>
<string name="shared_string_icon">アイコン</string>
<string name="shared_string_min_speed">最低速度</string>
<string name="shared_string_max_speed">最高速度</string>
<string name="default_speed_setting_title">標準移動速度</string>
<string name="shared_string_min_speed">予想最低速度</string>
<string name="shared_string_max_speed">予想最高速度</string>
<string name="default_speed_setting_title">予想標準速度</string>
<string name="default_speed_dialog_msg">すべての道路の移動速度を制限し、種別や制限速度が不明な道路が多い場合の到着時間予測に役立ちます(ルート計算に影響します)</string>
<string name="app_mode_offroad">オフロード</string>
<string name="edit_profile_setup_title">プロファイルの個別設定</string>
@ -3590,7 +3590,7 @@ POIの更新は利用できません</string>
<string name="create_edit_poi">POIの作成/編集</string>
<string name="parking_positions">駐車位置</string>
<string name="add_edit_favorite">お気に入りの追加/編集</string>
<string name="reset_deafult_order">アイテム順序をデフォルトに戻</string>
<string name="reset_deafult_order">アイテム順序を初期状態に戻しま</string>
<string name="back_to_editing">編集に戻る</string>
<string name="quick_action_switch_profile_descr">選択したプロファイルを切り替えるボタンです。</string>
<string name="shared_string_add_profile">プロファイルの追加</string>
@ -3667,12 +3667,12 @@ POIの更新は利用できません</string>
<string name="context_menu_actions">コンテキストメニュー</string>
<string name="reorder_or_hide_from">項目の並べ替えや非表示するものを指定できます。</string>
<string name="shared_string_divider">分割</string>
<string name="divider_descr">分割線で区切られた部分より下にある項目が適用されます。</string>
<string name="divider_descr">ここで指定された項目は、区切り線より下に配置されます。</string>
<string name="shared_string_hidden">非表示</string>
<string name="hidden_items_descr">これらの項目はメニューに表示されなくなりますが、オプションやプラグインはそのまま機能します。</string>
<string name="shared_string_items">項目</string>
<string name="reset_items_descr">設定を非表示にすると、元の状態にリセットされます。</string>
<string name="main_actions_descr">ボタンは4つしかありません。</string>
<string name="main_actions_descr">ボタンの数は4つ固定で変更できません。</string>
<string name="main_actions">主要機能</string>
<string name="additional_actions_descr">“%1$s”ボタンをタップすると、これらの機能にアクセスできます。</string>
<string name="move_inside_category">アイテムはこのカテゴリ内でのみ移動できます。</string>
@ -3794,7 +3794,7 @@ POIの更新は利用できません</string>
\n
\n国の法律に基づいて、使用を望むかどうかを決定する必要があります。
\n
\n%1$sを選択すると、スピードカメラに関するアラートと警告が表示されます。
\n%1$sを選択すると、スピードカメラに関するアラートと警告機能を使用できます。
\n
\n%2$sを選択すると、スピードカメラに関するすべてのデータ(警告、通知、POI)が、OsmAndの再インストールを行うまで削除されます。</string>
<string name="keep_active">機能を維持</string>

View file

@ -751,7 +751,7 @@
<string name="safe_mode">Sikker modus</string>
<string name="native_library_not_running">Programmet kjører i sikker modus (skru det av i \'Innstillinger\').</string>
<string name="interrupt_music_descr">Talemeldinger stopper midlertidig musikkavspilling.</string>
<string name="interrupt_music">Avbryt musikk</string>
<string name="interrupt_music">Sett musikk på pause</string>
<string name="gpxup_public">Offentlig</string>
<string name="rendering_attr_appMode_description">Optimaliser kart for</string>
<string name="rendering_attr_contourLines_description">Vis fra zoom-nivå (krever kotedata):</string>
@ -1650,7 +1650,7 @@
<string name="shared_string_selected">Valgt</string>
<string name="shared_string_selected_lowercase">valgte</string>
<string name="remove_the_tag">FJERN MERKELAPPEN</string>
<string name="version_settings_descr">Last ned nattlige utviklerversjoner.</string>
<string name="version_settings_descr">Last ned aktuelle utviklingsversjoner.</string>
<string name="version_settings">Byggversjoner</string>
<string name="proxy_pref_descr">Spesifiser en mellomtjener.</string>
<string name="logged_as">Innlogget som %1$s</string>
@ -2142,7 +2142,7 @@
<string name="quick_action_showhide_osmbugs_title">Vis/skjul OSM-notater</string>
<string name="quick_action_osmbugs_show">Vis OSM-notater</string>
<string name="quick_action_osmbugs_hide">Skjul OSM-notater</string>
<string name="quick_action_showhide_osmbugs_descr">Å trykke denne handlingsknappen viser eller skjuler OSM-notater på kartet.</string>
<string name="quick_action_showhide_osmbugs_descr">Knapp til å vise eller skjule OSM-notater på kartet.</string>
<string name="sea_depth_thanks">Takk for at du kjøpte \'Havdybdekonturer\'</string>
<string name="index_item_depth_contours_osmand_ext">Havdybdekonturer</string>
<string name="download_depth_countours">Havdybdekonturer</string>
@ -2257,17 +2257,17 @@
<string name="quick_action_edit_action">Rediger handling</string>
<string name="quick_actions_delete">Slett handling</string>
<string name="quick_favorites_name_preset">Navneforvalg</string>
<string name="quick_action_add_marker_descr">Trykk på denne handlingsknappen legger til en kartmarkør i skjermsenteret.</string>
<string name="quick_action_add_gpx_descr">Trykking på denne handlingsknappen legger til et GPX-rutepunkt i midten av skjermen.</string>
<string name="quick_action_take_audio_note_descr">Trykking på denne handlingsknappen legger til et lydnotat i midten av skjermen.</string>
<string name="quick_action_take_video_note_descr">Trykking på denne handlingsknappen legger til et videonotat i midten av skjermen.</string>
<string name="quick_action_take_photo_note_descr">Trykking på denne handlingsknappen legger til et bildenotat i midten av skjermen.</string>
<string name="quick_action_add_osm_bug_descr">Trykking på denne handlingsknappen legger til et OSM-notat i midten av skjermen.</string>
<string name="quick_action_navigation_voice_descr">Trykking på denne handlingsknappen slår av eller på taleveiledning under navigering.</string>
<string name="quick_action_add_parking_descr">Trykking på denne handlingsknappen legger til en parkeringsplass i midten av skjermen.</string>
<string name="quick_action_add_marker_descr">En knapp for å legge til en kartmarkør i skjermsenteret.</string>
<string name="quick_action_add_gpx_descr">En knapp for å legge til et GPX-rutepunkt i midten av skjermen.</string>
<string name="quick_action_take_audio_note_descr">En knapp for å legge til et lydnotat i midten av skjermen.</string>
<string name="quick_action_take_video_note_descr">En knapp for å legge til et videonotat i midten av skjermen.</string>
<string name="quick_action_take_photo_note_descr">En knapp for å legge til et bildenotat i midten av skjermen.</string>
<string name="quick_action_add_osm_bug_descr">En knapp for å legge til et OSM-notat i midten av skjermen.</string>
<string name="quick_action_navigation_voice_descr">En knapp til å slå av eller på taleveiledning under navigering.</string>
<string name="quick_action_add_parking_descr">En knapp for å legge til en parkeringsplass i midten av skjermen.</string>
<string name="quick_action_interim_dialog">Vis en midlertidig dialog</string>
<string name="favorite_autofill_toast_text">" lagret i "</string>
<string name="quick_action_showhide_favorites_descr">Trykking på denne handlingsknappen viser eller skjuler favorittpunktene på kartet.</string>
<string name="quick_action_showhide_favorites_descr">En knapp til å vise eller skjule favorittpunktene på kartet.</string>
<string name="quick_action_add_create_items">Opprett elementer</string>
<string name="quick_action_fav_name_descr">La stå tomt for å bruke adressen eller stedsnavnet.</string>
<string name="quick_action_bug_descr">Denne meldingen inkluderes i kommentarfeltet.</string>
@ -2341,10 +2341,10 @@
<string name="tap_on_map_to_hide_interface_descr">Et trykk på kartet skjuler/viser kontrollknappene og miniprogrammene.</string>
<string name="mark_passed">Marker som passert</string>
<string name="osn_modify_dialog_error">Kunne ikke endre notatet.</string>
<string name="quick_action_auto_zoom_desc">Trykking på denne handlingsknappen slår av/på automatisk kartforstørrelse i henhold til hastigheten din.</string>
<string name="quick_action_add_destination_desc">Trykking på denne handlingsknappen gjør skjermsenteret til rutemål, ethvert tidligere valgt reisemål blir det siste mellomliggende målet.</string>
<string name="quick_action_replace_destination_desc">Trykking på denne handlingsknappen gjør skjermsenteret det nye rutemålet, og erstatter det tidligere valgte reisemålet (hvis noe).</string>
<string name="quick_action_add_first_intermediate_desc">Trykking på denne handlingsknappen gjør skjermsenteret til det første mellomliggende reisemålet.</string>
<string name="quick_action_auto_zoom_desc">Knapp til å slå av eller på hastighetsbasert auto-zoom.</string>
<string name="quick_action_add_destination_desc">En knapp for å gjøre skjermsenteret til rutemålet, et tidligere valgt reisemål blir det siste mellomliggende målet.</string>
<string name="quick_action_replace_destination_desc">En knapp for å gjøre skjermsenteret til det nye rutemålet, erstatter det tidligere valgte reisemålet (hvis noe).</string>
<string name="quick_action_add_first_intermediate_desc">En knapp for å gjøre skjermsenteret til det første mellomliggende reisemålet.</string>
<string name="subscribe_email_desc">Abonner på vår e-postliste om programrabatter og få tre kartnedlastinger til!</string>
<string name="index_item_depth_points_southern_hemisphere">Havdybdepunkter for sørlige halvkule</string>
<string name="index_item_depth_points_northern_hemisphere">Havdybdepunkter for nordlige halvkule</string>
@ -2382,7 +2382,7 @@
<string name="activate_seamarks_plugin">Aktiver \"sjøkartvisning\" -tillegget</string>
<string name="save_poi_too_many_uppercase">Navnet inneholder for mange store bokstaver. Fortsett\?</string>
<string name="quick_action_add_poi">Legg til interessepunkt</string>
<string name="quick_action_showhide_poi_descr">Trykking på denne handlingsknappen viser eller skjuler interessepunkter på kartet.</string>
<string name="quick_action_showhide_poi_descr">En knapp til å vise eller skjule interessepunkter på kartet.</string>
<string name="quick_action_page_list_descr">En knapp til å bla gjennom listen nedenfor.</string>
<string name="quick_action_empty_param_error">Fyll ut alle parametere</string>
<string name="quick_action_btn_tutorial_descr">Ved å trykke lenge og dra knappen endres dens plassering på skjermen.</string>
@ -2408,7 +2408,7 @@
<string name="context_menu_points_of_group">Alle punkter i gruppen</string>
<string name="av_locations_selected_desc">GPX-fil med de valgte notatenes koordinater og data.</string>
<string name="av_locations_all_desc">GPX-fil med alle notaters koordinater og data.</string>
<string name="quick_action_add_poi_descr">Trykking på denne handlingsknappen legger til et interessepunkt i midten av skjermen.</string>
<string name="quick_action_add_poi_descr">En knapp for å legge til et interessepunkt i midten av skjermen.</string>
<string name="open_from">Åpen fra</string>
<string name="open_till">Åpen til</string>
<string name="will_close_at">Stenger</string>
@ -2859,10 +2859,10 @@
<string name="run_full_osmand_msg">Du bruker {0} kart levert av OsmAnd. Vil du starte fullversjonen av OsmAnd\?</string>
<string name="run_full_osmand_header">Kjør OsmAnd\?</string>
<string name="lang_gn_py">Guaraní</string>
<string name="quick_action_switch_day_night_descr">Vekselvender mellom dag- og nattmodus i OsmAnd</string>
<string name="quick_action_switch_day_night_descr">En knapp til å skifte mellom dag- og nattmodus i OsmAnd.</string>
<string name="quick_action_switch_day_mode">Dagmodus</string>
<string name="quick_action_switch_night_mode">Nattmodus</string>
<string name="quick_action_day_night_switch_mode">Veksle dag-/nattmodus</string>
<string name="quick_action_day_night_switch_mode">Bytt dag/natt-modus</string>
<string name="app_mode_public_transport">Offentlig transport</string>
<string name="add_destination_point">Sett reisemål</string>
<string name="add_intermediate_point">Legg til mellomliggende</string>
@ -3256,7 +3256,7 @@
<string name="exported_osmand_profile">OsmAnd-profil: %1$s</string>
<string name="profile_import">Profil-import</string>
<string name="rendering_value_white_name">Hvit</string>
<string name="default_speed_dialog_msg">Brukes til å estimere ankomsttid for ukjente veityper, og for å begrense farten på alle veier (kan endre rute)</string>
<string name="default_speed_dialog_msg">Estimerer ankomsttid for ukjente veityper og begrenser farten for alle veier (kan påvirke ruting)</string>
<string name="track_storage_directory">Spor-lagringsmappe</string>
<string name="track_storage_directory_descrp">Spor kan lagres i \'rec\'-mappen, månedlige eller daglige mapper.</string>
<string name="store_tracks_in_rec_directory">Ta opp spor til \'rec\'-mappen</string>
@ -3286,13 +3286,13 @@
\n
\n</string>
<string name="apply_preference_to_all_profiles">Du kan bruke denne endringen på alle eller bare på den valgte profilen.</string>
<string name="quick_action_contour_lines_descr">En bryter for å vise eller skjule koter på kartet.</string>
<string name="quick_action_contour_lines_descr">Knapp som viser eller skjuler koter på kartet.</string>
<string name="export_profile">Eksporter profil</string>
<string name="overwrite_profile_q">\"%1$s\" finnes allerede. Overskriv\?</string>
<string name="export_profile_failed">Kunne ikke eksportere profil.</string>
<string name="profile_import_descr">Legg til en profil ved å åpne profilfilen med OsmAnd.</string>
<string name="file_import_error">%1$s feil under import: %2$s</string>
<string name="file_imported_successfully">%1$s ble importert.</string>
<string name="file_import_error">%1$s importfeil: %2$s</string>
<string name="file_imported_successfully">%1$s importert.</string>
<string name="swap_two_places">Bytt %1$s og %2$s</string>
<string name="route_start_point">Startpunkt</string>
<string name="rendering_attr_ice_road_name">Isvei</string>
@ -3345,20 +3345,20 @@
<string name="navigate_point_format_olc">Åpen plasseringskode (OLC)</string>
<string name="coordinates_format_info">Valgt format vil benyttes i programmet.</string>
<string name="pref_selected_by_default_for_profiles">Denne innstillingen er valgt som forvalg for profiler: %s</string>
<string name="apply_to_current_profile">Bruk kun for «%1$s»</string>
<string name="apply_to_all_profiles">Bruk for alle profiler</string>
<string name="apply_to_current_profile">Bruk kun for \"%1$s\"</string>
<string name="apply_to_all_profiles">Bruk alle profiler</string>
<string name="analytics_pref_title">Analyseinstrumenter</string>
<string name="turn_screen_on_info">Vis kart på låseskjermen under navigering.</string>
<string name="route_parameters_info">Innstillinger for ruting i valgt profil «%1$s».</string>
<string name="route_parameters_info">Innstillinger for ruting i den valgte profilen \"%1$s\".</string>
<string name="wake_time">Tidsavbrudd etter oppvåkning</string>
<string name="screen_alerts_descr">Varsler vises nede til venstre under navigering.</string>
<string name="map_during_navigation_info">Kart under navigasjon</string>
<string name="map_during_navigation">Kart under navigasjon</string>
<string name="voice_announces_info">Stemmekunngjøringer finner kun sted under navigasjon.</string>
<string name="voice_announces_descr">Navigasjonsinstruks og kunngjøringer</string>
<string name="voice_announces">Stemmekunngjøringer</string>
<string name="voice_announces">Talemeldinger</string>
<string name="route_parameters_descr">Sett opp ruteparameter</string>
<string name="route_parameters">Ruteparameter</string>
<string name="route_parameters">Ruteparametere</string>
<string name="download_detailed_map">Last ned detaljert %s-kart for å vise dette området.</string>
<string name="rendering_attr_piste_type_sleigh_name">Slede</string>
<string name="rendering_attr_piste_type_sled_name">Akebrett</string>
@ -3398,13 +3398,13 @@
<string name="personal_category_name">Personlig</string>
<string name="shared_string_downloading_formatted">Laster ned %s</string>
<string name="rendering_value_thick_name">Tykk</string>
<string name="desert_render_descr">For ørkener og andre tynt befolkede områder. Høyere detaljnivå.</string>
<string name="desert_render_descr">For ørkener og andre tynt befolkede områder. Mer detaljert.</string>
<string name="select_navigation_icon">Posisjonsikon under bevegelse</string>
<string name="select_map_icon">Posisjonsikon i hviletilstand</string>
<string name="delete_profiles_descr">Trykk på \'Bruk\' sletter fjernede profiler permanent.</string>
<string name="master_profile">Hovedprofil</string>
<string name="select_color">Velg farge</string>
<string name="edit_profiles_descr">Du kan ikke slette forvalgsprofilene, men du kan skru dem av før dette steget, eller flytte dem til bunnen.</string>
<string name="edit_profiles_descr">OsmAnd-Standardprofiler kan ikke slettes, men deaktiveres (på forrige skjermbilde), eller bli sortert til bunnen.</string>
<string name="edit_profiles">Rediger profiler</string>
<string name="select_nav_profile_dialog_message">Navigasjonstype har innvirkning på regler for ruteberegning.</string>
<string name="profile_appearance">Profilutseende</string>
@ -3418,8 +3418,8 @@
<string name="ltr_or_rtl_combine_via_space">%1$s %2$s</string>
<string name="import_profile">Importer profil</string>
<string name="app_mode_osm">OSM</string>
<string name="file_does_not_contain_routing_rules">\"%1$s\"-filen inneholder ingen ruteplanleggingsregler, velg en annen fil.</string>
<string name="not_support_file_type_with_ext">Ustøttet filtype. Du må velge en fil med %1$s-filendelse.</string>
<string name="file_does_not_contain_routing_rules">Ingen rutingsregler i \'%1$s\'. Velg en annen fil.</string>
<string name="not_support_file_type_with_ext">Velg en støttet fil med %1$s-endelse isteden.</string>
<string name="import_from_file">Importer fra fil</string>
<string name="import_routing_file">Importer ruteplanleggingsfil</string>
<string name="monitoring_prefs_descr">Navigasjon, loggingsnøyaktighet</string>
@ -3429,7 +3429,7 @@
<string name="live_monitoring_descr">Tillater deg å dele nåværende plassering ved bruk av turopptak.</string>
<string name="live_monitoring">Nettbasert sporing</string>
<string name="save_track_logging_accuracy">Loggingsnøyaktighet</string>
<string name="tracks_view_descr">Du kan finne alle dine innspilte spor i %1$s, eller i OsmAnd-mappen.</string>
<string name="tracks_view_descr">Dine innspilte spor er i %1$s, eller i OsmAnd-mappen.</string>
<string name="multimedia_notes_view_descr">Du finner alle dine OSM-notater i %1$s.</string>
<string name="video_notes">Videonotater</string>
<string name="photo_notes">Bildenotater</string>
@ -3601,7 +3601,7 @@
<string name="logcat_buffer_descr">Sjekk og del detaljert loggføring fra programmet</string>
<string name="use_system_screen_timeout">Bruk systemets skjermtidsavbrudd</string>
<string name="plugin_disabled">Programtillegg av</string>
<string name="no_recalculation_setting">Ingen omregning</string>
<string name="no_recalculation_setting">Ingen ny beregning</string>
<string name="please_provide_profile_name_message">Angi et navn for profilen</string>
<string name="select_data_to_import">Velg data å importere.</string>
<string name="slope_read_more">Du kan lese mer om løyper i %1$s.</string>
@ -3627,7 +3627,7 @@
\nSkru av ubrukte programtillegg for å skjule alle deres styringskontroller. %1$s.</string>
<string name="hidden_items_descr">Disse elementene er skjult fra menyen, men de representerte valgene eller programtilleggene vil fortsette å virke.</string>
<string name="select_wikipedia_article_langs">Velg språkene Wikipedia-artikler skal vises på i kartet. Du kan bytte mellom alle tilgjengelige språk mens du leser artikkelen.</string>
<string name="legend_item_description">Veiledning til kartets merking.</string>
<string name="legend_item_description">Veiledning til kartets symbolbruk.</string>
<string name="shared_string_routing">Ruteplanlegging</string>
<string name="route_recalculation_dist_title">Minsteavstand for å beregne rute på nytt</string>
<string name="import_duplicates_description">OsmAnd har allerede elementer med samme navn som de i importen.
@ -3662,7 +3662,7 @@
<string name="speed_camera_pois">Fotoboks-interessepunkter</string>
<string name="shared_string_uninstall">Avinstaller</string>
<string name="vessel_height_warning">Du kan sette fartøyhøyde for å unngå lave broer. Hvis broen endrer høyde, brukes høyden i åpen tilstand.</string>
<string name="quick_action_remove_next_destination">Slett neste målpunkt</string>
<string name="quick_action_remove_next_destination">Slett nærmeste målpunkt</string>
<string name="please_provide_point_name_error">Navngi punktet</string>
<string name="quick_action_showhide_mapillary_title">Vis/skjul Mapillary</string>
<string name="quick_action_mapillary_hide">Skjul Mapillary</string>
@ -3675,19 +3675,19 @@
<string name="listed_exist"/>
<string name="turn_screen_on_navigation_instructions">Navigasjonsinstruks</string>
<string name="speed_cameras_restart_descr"/>
<string name="shared_string_uninstall_and_restart"/>
<string name="shared_string_uninstall_and_restart">Avinstaller og start på nytt</string>
<string name="quick_action_remove_next_destination_descr"/>
<string name="app_mode_wheelchair">Rullestol</string>
<string name="app_mode_go_cart">Gokart</string>
<string name="plan_a_route">Planlegg en rute</string>
<string name="additional_actions_descr">Du får tilgang til disse handlingene ved å trykke på knappen “%1$s”.</string>
<string name="use_volume_buttons_as_zoom_descr">Slå på for å stille inn zoomnivået på kartet med enhetens volumknapper.</string>
<string name="use_volume_buttons_as_zoom_descr">Styr zoomnivået på kartet med enhetens volumknapper.</string>
<string name="add_hidden_group_info">Det tillagte punktet vil ikke være synlig på kartet, siden den valgte gruppen er skjult, du kan finne det i \"%s\".</string>
<string name="app_mode_motor_scooter">Scooter</string>
<string name="app_mode_wheelchair_forward">Rullestol framover</string>
<string name="set_working_days_to_continue">Du må definere arbeidsdagene for å fortsette</string>
<string name="set_working_days_to_continue">Still inn arbeidsdager for å fortsette</string>
<string name="add_to_a_track">Legg til i et spor</string>
<string name="track_show_start_finish_icons">Vis ikoner for start-mål</string>
<string name="track_show_start_finish_icons">Vis ikoner for start og mål</string>
<string name="select_track_width">Velg bredde</string>
<string name="gpx_split_interval_descr">Velg intervallet hvor markeringer med avstand eller tid på sporet vil vises.</string>
<string name="gpx_split_interval_none_descr">Velg det ønskede oppdelingsalternativet: etter tid eller etter avstand.</string>
@ -3702,7 +3702,7 @@
<string name="plan_route_last_edited">Sist redigert</string>
<string name="plan_route_import_track">Importer spor</string>
<string name="plan_route_open_existing_track">Åpne eksisterende spor</string>
<string name="plan_route_select_track_file_for_open">Velg en sporfil for åpning.</string>
<string name="plan_route_select_track_file_for_open">Velg en sporfil for å åpne.</string>
<string name="reverse_route">Snu rute</string>
<string name="overwrite_track">Overskriv spor</string>
<string name="route_between_points_whole_track_button_desc">Hele sporet blir beregnet på nytt med den valgte profilen.</string>
@ -3722,7 +3722,7 @@
<string name="empty_state_my_tracks_desc">Importer eller ta opp sporfiler</string>
<string name="follow_track">Følg spor</string>
<string name="follow_track_descr">Velg sporfil å følge</string>
<string name="import_track_descr">Velg sporfil å følge, eller importer en.</string>
<string name="import_track_descr">Velg sporfil å følge eller importer fra enheten din.</string>
<string name="select_another_track">Velg et annet spor</string>
<string name="start_of_the_track">Starten av sporet</string>
<string name="nearest_point">Nærmeste punkt</string>
@ -3747,4 +3747,15 @@
<string name="sort_last_modified">Sist endret</string>
<string name="sort_name_descending">Navn: Å - A</string>
<string name="sort_name_ascending">Navn: A - Å</string>
<string name="start_finish_icons">Start/mål-ikoner</string>
<string name="shared_string_save_as_gpx">Lagre som ny sporfil</string>
<string name="add_segment_to_the_track">Legg til i en sporfil</string>
<string name="shared_string_gpx_files">Spor</string>
<string name="layer_gpx_layer">Spor</string>
<string name="show_gpx">Spor</string>
<string name="map_widget_monitoring">Turopptak</string>
<string name="marker_save_as_track">Lagre som sporfil</string>
<string name="number_of_gpx_files_selected_pattern">%s sporfiler valgt</string>
<string name="gpx_monitoring_stop">Sett turopptak på pause</string>
<string name="gpx_monitoring_start">Gjenoppta turopptak</string>
</resources>

View file

@ -3812,7 +3812,7 @@
<string name="poi_tactile_paving_contrasted">Skontrastowane</string>
<string name="poi_drinking_water_refill_network">Uzupełnianie wody pitnej: woda z sieci</string>
<string name="poi_drinking_water_refill_no">Uzupełnianie wody pitnej: nie</string>
<string name="poi_drinking_water_refill_yes">Uzupełnianie wody pitnej: tak</string>
<string name="poi_drinking_water_refill_yes">Tak</string>
<string name="poi_seamark_water_level_floating">Poziom wody: utrzymujący się na powierzchni</string>
<string name="poi_seamark_water_level_below_mwl">Poziom wody: poniżej średniego poziomu wody</string>
<string name="poi_seamark_water_level_awash">Poziom wody: obmywający falami</string>
@ -3834,4 +3834,7 @@
<string name="poi_pump_status_missing_beam">Stan pompy: brak wiązki</string>
<string name="poi_traffic_signals_arrow_no">Strzałka: nie</string>
<string name="poi_elevator">Winda</string>
<string name="poi_recycling_small_electrical_appliances">Małogabarytowe urządzenia elektryczne</string>
<string name="poi_departures_board">Tablica odjazdów/odlotów</string>
<string name="poi_drinking_water_refill">Uzupełnianie wody pitnej</string>
</resources>

View file

@ -3844,4 +3844,5 @@
<string name="poi_recycling_small_electrical_appliances">Pequenos aparelhos elétricos</string>
<string name="poi_beehive">Colmeia</string>
<string name="poi_nuts">Loja de nozes</string>
<string name="poi_fuel_lng">GNL</string>
</resources>

View file

@ -3899,4 +3899,5 @@
<string name="sort_last_modified">Última modificação</string>
<string name="sort_name_descending">Nome: Z A</string>
<string name="sort_name_ascending">Nome: A Z</string>
<string name="start_finish_icons">Ícones de início/término</string>
</resources>

View file

@ -436,7 +436,7 @@
<string name="poi_telephone_exchange">Central telefônica</string>
<string name="poi_recycling">Reciclagem</string>
<string name="poi_recycling_centre">Centro de reciclagem</string>
<string name="poi_recycling_container">Contêiner</string>
<string name="poi_recycling_container">Contentor</string>
<string name="poi_recycling_glass">Vidro</string>
<string name="poi_recycling_paper">Papel</string>
<string name="poi_recycling_clothes">Roupas</string>
@ -1775,7 +1775,7 @@
<string name="poi_vending_toys">Brinquedos</string>
<string name="poi_vending_ice_cream">Sorvete</string>
<string name="poi_vending_sim_cards">Cartão SIM</string>
<string name="poi_branch">Seção</string>
<string name="poi_branch">Secção</string>
<string name="poi_memorial_war">Memorial de guerra</string>
<string name="poi_memorial_plaque">Placa comemorativa</string>
<string name="poi_memorial_statue">Estátua</string>
@ -2400,7 +2400,7 @@
<string name="poi_cargo_passengers">Passageiros</string>
<string name="poi_cargo_vehicle">Veículos</string>
<string name="poi_cargo_bicycle">Bicicletas</string>
<string name="poi_cargo_container">Contêineres</string>
<string name="poi_cargo_container">Contentor</string>
<string name="poi_cargo_hgv">Veículos pesados</string>
<string name="poi_fitness_station">Academia ao ar livre</string>
<string name="poi_hackerspace">Hackerspace</string>
@ -3829,4 +3829,5 @@
<string name="poi_recycling_small_electrical_appliances">Pequenos aparelhos elétricos</string>
<string name="poi_beehive">Colmeia</string>
<string name="poi_nuts">Loja de nozes</string>
<string name="poi_fuel_lng">GNL</string>
</resources>

View file

@ -239,7 +239,7 @@
<string name="recalculate_route_to_your_location">Meio de transporte:</string>
<string name="mark_final_location_first">Por favor, define o destino primeiro</string>
<string name="get_directions">Navegação</string>
<string name="gps_status_app_not_found">A aplicação do estado do GPS não está instalada. Pesquisar na loja de aplicações\?</string>
<string name="gps_status_app_not_found">A app do estado do GPS não está instalada. Pesquisar na loja de apps\?</string>
<string name="opening_hours">Horas de abertura</string>
<string name="opening_changeset">Abrindo conjunto de alterações …</string>
<string name="closing_changeset">Fechando conjunto de alterações…</string>
@ -294,7 +294,7 @@
<string name="shared_string_add_to_favorites">Adicionar aos \'Favoritos\'</string>
<string name="use_english_names_descr">Escolher entre os nomes nativos e inglês.</string>
<string name="use_english_names">Usar nomes em inglês</string>
<string name="app_settings">Configurações da aplicação</string>
<string name="app_settings">Configurações da app</string>
<string name="search_address">Pesquisar endereço</string>
<string name="choose_building">Escolher edifício</string>
<string name="choose_street">Escolher rua</string>
@ -486,7 +486,7 @@
<string name="use_fluorescent_overlays_descr">Usar cores fluorescentes para mostrar trajetos e rotas.</string>
<string name="offline_edition">Edição offline</string>
<string name="offline_edition_descr">Usar sempre a edição offline.</string>
<string name="update_poi_does_not_change_indexes">As alterações de POI dentro da aplicação não afetam os ficheiros de mapas descarregados; essas alterações são guardadas num ficheiro separado no seu aparelho.</string>
<string name="update_poi_does_not_change_indexes">As alterações de POI dentro da app não afetam os ficheiros de mapas descarregados; essas alterações são guardadas num ficheiro separado no seu aparelho.</string>
<string name="local_openstreetmap_uploading">A enviar…</string>
<string name="local_openstreetmap_were_uploaded">{0} POI/anotações enviados</string>
<string name="local_openstreetmap_uploadall">Enviar todos</string>
@ -523,7 +523,7 @@
<string name="fav_export_confirmation">Já existe um ficheiro de favoritos exportados anteriormente. Quer substitui-lo\?</string>
<string name="profile_settings">Configurações específicas de Perfil</string>
<string name="global_settings">Configurações Globais</string>
<string name="global_app_settings">Configurações globais da aplicação</string>
<string name="global_app_settings">Configurações globais da app</string>
<string name="download_files_not_enough_space">Espaço livre insuficiente, precisa de %1$s MB (só tem: %2$s disponíveis).</string>
<string name="download_files_question_space">Descarregar {0} ficheiro(s)\?
\n {1} MB (de {2} MB) será utilizado.</string>
@ -554,7 +554,7 @@
<string name="old_poi_file_should_be_deleted">O ficheiro POI \'%1$s\' é redundante e pode ser eliminado.</string>
<string name="update_poi_file_not_found">Não foi encontrado (e não pôde ser criado) o ficheiro local para guardar as mudanças de POI.</string>
<string name="button_upgrade_osmandplus">Upgrade para OsmAnd+</string>
<string name="map_version_changed_info">Descarregue a nova versão da aplicação para poder usar os novos ficheiros de mapas.</string>
<string name="map_version_changed_info">Descarregue a nova versão da app para poder usar os novos ficheiros de mapas.</string>
<string name="shared_string_rename">Mudar o nome</string>
<string name="poi_filter_nominatim">Online Nomeação</string>
<string name="search_position_current_location_search">Procurando posição…</string>
@ -624,7 +624,7 @@
<string name="voice_stream_voice_call">Áudio de chamada telefónica (para interromper os aparelhos de som Bluetooth do carro)</string>
<string name="voice_stream_notification">Áudio de Notificação</string>
<string name="voice_stream_music">Áudio de mídia/navegação</string>
<string name="warning_tile_layer_not_downloadable">A aplicação não conseguiu descarregar a camada do mapa %1$s, se a tornar a instalar pode resolver o problema.</string>
<string name="warning_tile_layer_not_downloadable">A app não conseguiu descarregar a camada do mapa %1$s, se a tornar a instalar pode resolver o problema.</string>
<string name="overlay_transparency_descr">Ajustar a transparência da sobreposição.</string>
<string name="overlay_transparency">Transparência da Sobreposição</string>
<string name="map_transparency_descr">Ajustar a transparência do mapa base.</string>
@ -646,7 +646,7 @@
<string name="error_doing_search">Não foi possível executar a pesquisa offline.</string>
<string name="search_osm_offline">Pesquisa por localização geográfica</string>
<string name="system_locale">Sistema</string>
<string name="preferred_locale_descr">Idioma de exibição da aplicação (usado após OsmAnd ser reiniciado).</string>
<string name="preferred_locale_descr">Idioma de exibição da app (usado após OsmAnd ser reiniciado).</string>
<string name="preferred_locale">Linguagem</string>
<string name="shared_string_next">Próximo</string>
<string name="shared_string_previous">Anterior</string>
@ -671,7 +671,7 @@
\nO serviço de navegação está temporariamente mudado para CloudMade on-line.</string>
<string name="specified_dir_doesnt_exist">Não foi possível encontrar a pasta especificada.</string>
<string name="application_dir">Local de armazenamento</string>
<string name="osmand_net_previously_installed">Todos os dados offline na aplicação instalada antiga serão suportados pela nova aplicação, mas os pontos Favoritos devem ser exportados da aplicação antiga e depois importados na nova aplicação.</string>
<string name="osmand_net_previously_installed">Todos os dados offline na app instalada antiga serão suportados pela nova, mas os pontos Favoritos devem ser exportados da app antiga e depois importados na nova.</string>
<string name="build_installed">Build {0} foi instalado ({1}).</string>
<string name="downloading_build">Descarregando construção…</string>
<string name="install_selected_build">Instalar OsmAnd - {0} de {1} {2} MB \?</string>
@ -789,12 +789,12 @@
<string name="email">e-mail</string>
<string name="osmand_long_description_1000_chars">OsmAnd (direções automatizadas de navegação OSM)
\n
\nO OsmAnd é uma aplicação de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz.
\nO OsmAnd é uma app de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz.
\n
\nAlgumas das características principais:
\n- Funcionalidade totalmente desligado da Internet (guarda os mapas obtidos, sejam eles vetoriais ou imagens, numa pasta selecionável).
\n- Mapas vetoriais compactados do mundo inteiro disponíveis.
\n- Descarregar mapas de países ou regiões diretamente na aplicação.
\n- Descarregar mapas de países ou regiões diretamente na app.
\n- Sobreposição de mapas diversos, como GPX ou trajetos de navegação, pontos de interesse (POI), favoritos, curvas de nível, paragens de transportes públicos, mapas adicionais com transparência personalizável.
\n- Pesquisa desligado da Internet para endereços e locais (POIs).
\n- Encaminhamento desligado da Internet para distâncias médias.
@ -846,9 +846,9 @@
<string name="download_roads_only_item">Só Estradas</string>
<string name="download_regular_maps">Mapa padrão</string>
<string name="download_roads_only_maps">Mapas só de estradas</string>
<string name="safe_mode_description">Executar a aplicação no modo de segurança (usando o código do Android mais lento em vez do nativo).</string>
<string name="safe_mode_description">Executar a app no modo de segurança (usando o código do Android mais lento em vez do nativo).</string>
<string name="safe_mode">Modo seguro</string>
<string name="native_library_not_running">A aplicação está a ser executada no modo de segurança (desligue-a em \"Definições\").</string>
<string name="native_library_not_running">A app está a ser executada no modo de segurança (desligue-a em \"Definições\").</string>
<string name="background_service_is_enabled_question">O serviço de segundo plano OsmAnd ainda está em execução. Tambẽm pará-lo\?</string>
<string name="close_changeset">Fechar conjunto de alterações</string>
<string name="search_villages_and_postcodes">Pesquisar mais povoações/códigos postais</string>
@ -893,7 +893,7 @@
<string name="av_def_action_choose">Sob demanda\?</string>
<string name="av_video_format_descr">Formato de saída de vídeo:</string>
<string name="av_use_external_recorder_descr">Usar gravador do sistema para vídeo.</string>
<string name="av_use_external_camera_descr">Utilize aplicação do sistema para fotos.</string>
<string name="av_use_external_camera_descr">Utilize a app do sistema para fotos.</string>
<string name="av_use_external_camera">Usar aplicação da câmara</string>
<string name="recording_playing">A tocar o áudio da gravação. \n%1$s</string>
<string name="recording_unavailable">Indisponível</string>
@ -927,14 +927,14 @@
<string name="item_unchecked">desmarcado</string>
<string name="map_widget_max_speed">Limite de Velocidade</string>
<string name="no_buildings_found">Nenhum edifício encontrado.</string>
<string name="zxing_barcode_scanner_not_found">A aplicação do leitor de código de barras ZXing não está instalada. Procurar no Google Play\?</string>
<string name="zxing_barcode_scanner_not_found">A app do leitor de código de barras ZXing não está instalada. Procurar no Google Play\?</string>
<string name="support_new_features_descr">Faça uma doação para ver novas funcionalidades implementadas nesta aplicação.</string>
<string name="incomplete_locale">incompleto</string>
<string name="street_name">Nome da rua</string>
<string name="hno">Número de casa</string>
<string name="monitoring_settings">Gravação de viagem</string>
<string name="choose_osmand_theme_descr">Personalizar a aparência da aplicação.</string>
<string name="choose_osmand_theme">Tema da aplicação</string>
<string name="choose_osmand_theme_descr">Personalizar a aparência da app.</string>
<string name="choose_osmand_theme">Tema da app</string>
<string name="accessibility_options">Opções de acessibilidade</string>
<string name="select_address_activity">Especifique um endereço</string>
<string name="favourites_list_activity">Selecione favorito</string>
@ -1047,14 +1047,14 @@
<string name="base_world_map">Mapa mundial</string>
<string name="osmand_plus_long_description_1000_chars">OsmAnd+ (Direções de Navegação Automatizada do OSM)
\n
\n OsmAnd+ é uma aplicação de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz.
\n OsmAnd+ é uma app de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz.
\n
\n OsmAnd+ é a versão paga da aplicação, ao comprá-lo está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações.
\n OsmAnd+ é a versão paga da app, ao comprá-lo está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações.
\n
\n Algumas das características principais:
\n - Funcionalidade totalmente desligado da Internet (guarda os mapas obtidos, sejam eles vetoriais ou imagens, numa pasta selecionável).
\n - Mapas vetoriais compactados do mundo inteiro disponíveis.
\n - Descarregamento de mapas de países ou regiões diretamente na aplicação.
\n - Descarregamento de mapas de países ou regiões diretamente na app.
\n - Recurso Wikipédia desligado da Internet (descarregamento de POIs da Wikipédia), ótimo para passeios turísticos.
\n - Possibilidade de sobreposição de várias camadas de mapas, como trilhos GPX ou navegação, pontos de Interesse, favoritos, curvas de nível, paragens de transporte público, mapas adicionais com transparência personalizável.
\n
@ -1086,7 +1086,7 @@
<string name="amenity_type_osmwiki">Wikipédia (off-line)</string>
<string name="amenity_type_seamark">Marca Marítima</string>
<string name="app_modes_choose_descr">Escolha perfis visíveis.</string>
<string name="app_modes_choose">Perfis da aplicação</string>
<string name="app_modes_choose">Perfis da app</string>
<string name="route_descr_destination">Destino</string>
<string name="rendering_value_pink_name">Rosa</string>
<string name="rendering_value_brown_name">Castanho</string>
@ -1341,7 +1341,7 @@
<string name="watch">Ver</string>
<string name="navigate_point_northing">Norte</string>
<string name="navigate_point_easting">Leste</string>
<string name="storage_directory_internal_app">Memória interna da aplicação</string>
<string name="storage_directory_internal_app">Memória interna da app</string>
<string name="shared_string_go">Ir</string>
<string name="routing_settings_2">Configurações de navegação</string>
<string name="general_settings_2">Configurações gerais</string>
@ -1521,8 +1521,8 @@
<string name="map_downloaded_descr">O mapa %1$s está pronto para ser usado.</string>
<string name="map_downloaded">Mapa descarregado</string>
<string name="go_to_map">Mostrar mapa</string>
<string name="simulate_initial_startup_descr">Define o sinalizador que indica a primeira inicialização da aplicação, mantém todas as outras configurações inalteradas.</string>
<string name="simulate_initial_startup">Simular arranque inicial da aplicação</string>
<string name="simulate_initial_startup_descr">Define o sinalizador que indica a primeira inicialização da app, mantém todas as outras configurações inalteradas.</string>
<string name="simulate_initial_startup">Simular arranque inicial da app</string>
<string name="share_geo">geo:</string>
<string name="share_menu_location">Partilhar Localização</string>
<string name="shared_string_send">Enviar</string>
@ -1572,7 +1572,7 @@
<string name="select_voice_provider">Escolher orientação por voz</string>
<string name="select_voice_provider_descr">Escolher ou descarregar a orientação por voz para o seu idioma.</string>
<string name="night">Noite</string>
<string name="dashboard_or_drawer_description">Há uma nova opção para controlar principalmente a aplicação através do painel de controlo flexível ou um menu estático. A sua escolha pode ser alterada nas configurações do painel.</string>
<string name="dashboard_or_drawer_description">Há uma nova opção para controlar principalmente a app através do painel de controlo flexível ou um menu estático. A sua escolha pode ser alterada nas configurações do painel.</string>
<string name="dashboard_or_drawer_title">Painel de controlo ou menu de controlo</string>
<string name="update">Atualizar</string>
<string name="only_download_over_wifi">Apenas descarregar com Wi-Fi</string>
@ -1661,7 +1661,7 @@
<string name="rendering_value_thin_name">Fino</string>
<string name="rendering_value_medium_name">Média</string>
<string name="rendering_value_bold_name">Negrito</string>
<string name="storage_permission_restart_is_required">Agora a aplicação está autorizada a escrever no armazenamento externo, mas primeiro é necessário reiniciar a aplicação.</string>
<string name="storage_permission_restart_is_required">Agora a app está autorizada a escrever no armazenamento externo, mas primeiro é necessário reiniciar a app.</string>
<string name="shared_string_move_up">Mover ↑</string>
<string name="shared_string_move_down">Mover ↓</string>
<string name="finish_navigation">Terminar a navegação</string>
@ -1959,7 +1959,7 @@
<string name="no_overlay">Sem sobreposição</string>
<string name="no_underlay">Sem subposição</string>
<string name="subscribe_email_error">Erro</string>
<string name="subscribe_email_desc">Assine a nossa lista de e-mail sobre descontos da aplicação e ganhe mais 3 descarregamentos de mapas!</string>
<string name="subscribe_email_desc">Assine a nossa lista de e-mail sobre descontos da app e ganhe mais 3 descarregamentos de mapas!</string>
<string name="depth_contour_descr">Curvas de nível de profundidade marítima e seamarks.</string>
<string name="sea_depth_thanks">Muito obrigado por comprar \'Contornos de profundidade náutica\'</string>
<string name="index_item_depth_contours_osmand_ext">Contornos de profundidade náutica</string>
@ -1973,10 +1973,10 @@
<string name="fonts_header">Fontes do mapa</string>
<string name="right_side_navigation">Circulação pela direita</string>
<string name="driving_region_automatic">Automático</string>
<string name="do_not_send_anonymous_app_usage">Não envie estatísticas anónimas de utilização da aplicação</string>
<string name="do_not_send_anonymous_app_usage_desc">OsmAnd recolhe informação sobre as secções da aplicação que abriu. Não são enviadas: a sua localização; a informação que introduz na aplicação; detalhes de áreas que veja, procure ou descarregue.</string>
<string name="do_not_send_anonymous_app_usage">Não envie estatísticas anónimas de utilização da app</string>
<string name="do_not_send_anonymous_app_usage_desc">OsmAnd recolhe informação sobre as secções da app que abriu. Não são enviadas: a sua localização; a informação que introduz na app; detalhes de áreas que veja, procure ou descarregue.</string>
<string name="do_not_show_startup_messages">Não mostrar mensagens ao iniciar</string>
<string name="do_not_show_startup_messages_desc">Nâo mostrar descontos da aplicação e mensagens de eventos locais especiais.</string>
<string name="do_not_show_startup_messages_desc">Nâo mostrar descontos da app e mensagens de eventos locais especiais.</string>
<string name="parking_options">Opções de estacionamento</string>
<string name="full_version_thanks">Muito obrigado por comprar a versão paga de OsmAnd.</string>
<string name="routing_attr_relief_smoothness_factor_hills_name">Inclinado</string>
@ -2208,7 +2208,7 @@
<string name="how_to_open_link">Como abrir a hiperligação\?</string>
<string name="read_wikipedia_offline">Ler a Wikipédia desligado da Internet</string>
<string name="download_all">Descarregar tudo</string>
<string name="shared_string_restart">Reiniciar a aplicação</string>
<string name="shared_string_restart">Reiniciar a app</string>
<string name="show_images">Mostrar imagens</string>
<string name="purchase_cancelled_dialog_title">Cancelou a sua assinatura do OsmAnd Live</string>
<string name="purchase_cancelled_dialog_descr">Renovar assinatura para continuar a utilizar todas as funcionalidades:</string>
@ -2232,7 +2232,7 @@
<string name="wikivoyage_travel_guide_descr">Guias para os lugares mais interessantes do mundo dentro do OsmAnd, sem uma conexão com a Internet.</string>
<string name="monthly_map_updates">Atualizações de mapa mensais</string>
<string name="daily_map_updates">Atualizações de mapa a cada hora</string>
<string name="in_app_purchase">Compra na aplicação</string>
<string name="in_app_purchase">Compra na app</string>
<string name="in_app_purchase_desc">Pagamento de uma só vez</string>
<string name="in_app_purchase_desc_ex">Uma vez comprado, estará sempre disponível para si.</string>
<string name="purchase_unlim_title">Comprar - %1$s</string>
@ -2475,7 +2475,7 @@
\nRepresenta área: %1$s x %2$s</string>
<string name="speed_limit_exceed">Tolerância do limite de velocidade</string>
<string name="speed_limit_exceed_message">Selecione a margem de tolerância de limite de velocidade, acima do qual receberá um aviso de voz.</string>
<string name="fav_point_emoticons_message">O nome do Favorito foi modificado para %1$s para facilitar gravar corretamente a sequência de caracteres com emoticons para um ficheiro.</string>
<string name="fav_point_emoticons_message">O nome do favorito foi modificado para %1$s para facilitar gravar corretamente a cadeia de caracteres com emoticons num ficheiro.</string>
<string name="print_route">Imprimir rota</string>
<string name="fav_point_dublicate">Nome de favorito duplicado</string>
<string name="fav_point_dublicate_message">Nome favorito especificado já está em uso, foi alterado para %1$s para evitar a duplicação.</string>
@ -2551,7 +2551,7 @@
<string name="rendering_attr_alpineHiking_description">Renderizar caminhos de acordo com a escala de SAC.</string>
<string name="rendering_attr_hikingRoutesOSMC_description">Renderizar caminhos de acordo com traços OSMC.</string>
<string name="map_widget_intermediate_time">Hora intermediária</string>
<string name="osmand_extended_description_part1">OsmAnd (sigla em inglês de direções de navegação automatizada do OSM) é uma aplicação de mapas e navegação com acesso a dados livres, mundiais e de alta qualidade do OSM.
<string name="osmand_extended_description_part1">OsmAnd (sigla em inglês de direções de navegação automatizada do OSM) é uma app de mapas e navegação com acesso a dados livres, mundiais e de alta qualidade do OSM.
\n
\nPoderá usar o navegador visual e por voz, ver POIs (pontos de interesse), criar e gerir trilhos GPX, usar (através de um suplemento) curvas de nível e dados de altitude, escolher entre os modos motorista, ciclista e pedestre, editar o OpenStreetMap e muito mais.</string>
<string name="osmand_extended_description_part2">Navegação GPS
@ -2595,7 +2595,7 @@
\n • Envie trilhos GPX para o OpenStretMap diretamente da aplicação
\n • Adicione POIs e envie-os diretamente para o OpenStretMap (ou mais tarde se estiver desconectado da Internet)
\n</string>
<string name="osmand_extended_description_part8">OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a aplicação reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades.
<string name="osmand_extended_description_part8">OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a app reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades.
\n Cobertura de mapa e qualidade aproximada:
\n • Europa Ocidental: ****
\n • Europa Oriental: ***
@ -2609,10 +2609,10 @@
\n • Antártida: *
\n A maioria dos países ao redor do globo está disponível para descarregar!
\n Obtenha um navegador confiável no seu país - seja em França, Alemanha, México, Reino Unido, Espanha, Holanda, EUA, Rússia, Brasil ou qualquer outro.</string>
<string name="osmand_plus_extended_description_part1">OsmAnd+ (direções de navegação automatizada do OSM) é uma aplicação de mapas e navegação com acesso a dados livres do OSM, de todo o mundo e de alta qualidade.
\nDesfrute da navegação visual ou por voz, vendo POIs (pontos de interesse), criando e gerindo trilhos GPX, usando informação de altitude e curvas de nível, escolher entre modos dirigir, andar de bicicleta e pedestre, editar o OpenStreetMap e muito mais.
<string name="osmand_plus_extended_description_part1">OsmAnd+ (direções de navegação automatizada do OSM) é uma app de mapas e navegação com acesso a dados livres do OSM, de todo o mundo e de alta qualidade.
\nDesfrute da navegação visual ou por voz, ver POIs (pontos de interesse), criando e gerindo trilhos GPX, usando informação de altitude e curvas de nível, escolher entre modos dirigir, andar de bicicleta e pedestre, editar o OpenStreetMap e muito mais.
\n
\nOsmAnd+ é a versão paga da aplicação. Ao comprá-lo, está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações.
\nOsmAnd+ é a versão paga da app. Ao comprá-lo, está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações.
\n
\nAlgumas das características principais:</string>
<string name="osmand_plus_extended_description_part2">Navegação
@ -2654,10 +2654,10 @@
\n• Visualização de curvas de nível e sombreamento de relevo (via suplemento adicional)</string>
<string name="osmand_plus_extended_description_part7">Contribua diretamente para o OpenStreetMap
\n • Envie relatórios de erros.
\n • Envie trilhos GPX para o OpenStretMap diretamente da aplicação.
\n • Envie trilhos GPX para o OpenStretMap diretamente da app.
\n • Adicione POIs e envie-os diretamente para o OpenStretMap (ou mais tarde se estiver desconectado da Internet).
\n • Gravação de viagem opcional também em plano de fundo (enquanto o aparelho está no modo adormecido).
\n OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a aplicação reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades.
\n OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a app por reportar erros, a melhorar as traduções ou a programar novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades.
\n</string>
<string name="osmand_plus_extended_description_part8">Cobertura de mapa e qualidade aproximada:
\n• Europa Ocidental: ****
@ -3358,7 +3358,7 @@
<string name="delete_profiles_descr">Tocar em \'Aplicar\' apagará os perfis removidos permanentemente.</string>
<string name="master_profile">Perfil principal</string>
<string name="select_color">Selecione a cor</string>
<string name="edit_profiles_descr">Perfis padrão do OsmAnd não podem ser apagados, mas desativados (na tela anterior) ou classificados na parte inferior.</string>
<string name="edit_profiles_descr">Perfis predefinidos do OsmAnd não podem ser apagados, mas desativados (no ecrã anterior) ou classificados na parte inferior.</string>
<string name="edit_profiles">Editar perfis</string>
<string name="select_nav_profile_dialog_message">O \'Tipo de navegação\' controla como as rotas são calculadas.</string>
<string name="profile_appearance">Aspeto do perfil</string>
@ -3887,7 +3887,7 @@
<string name="simplified_track_description">Apenas a linha da rota será gravada, os pontos de passagem serão apagados.</string>
<string name="shared_string_file_name">Nome do ficheiro</string>
<string name="number_of_gpx_files_selected_pattern">%s ficheiros de faixa selecionados</string>
<string name="disable_recording_once_app_killed_descrp">Vai pausar o registo de faixas quando a aplicação for morta (através de aplicações recentes). (indicação de fundo de OsmAnd desaparece da barra de notificação do Android.)</string>
<string name="disable_recording_once_app_killed_descrp">Vai pausar o registo de faixas quando a app for morta (através de apps recentes). (indicação de fundo de OsmAnd desaparece da barra de notificação do Android.)</string>
<string name="release_3_8">- Função atualizada de Planear uma rota: permite utilizar diferentes tipos de navegação por segmento e a inclusão de faixas
\n
\n - Novo menu Aparência para trilhos: selecionar cor, espessura, setas de direção de visualização, ícones de início/fim
@ -3903,4 +3903,8 @@
\n - Problemas com as configurações de importação/exportação de perfis resolvidos
\n
\n</string>
<string name="sort_last_modified">Última modificação</string>
<string name="sort_name_descending">Nome: Z A</string>
<string name="sort_name_ascending">Nome: A Z</string>
<string name="start_finish_icons">Ícones de início/fim</string>
</resources>

View file

@ -71,7 +71,7 @@
<string name="nearest_cities">Ближайшие города</string>
<string name="select_city">Выберите город</string>
<string name="select_postcode">Поиск почтового индекса</string>
<string name="quick_action_take_audio_note">Аудио⁣заметка</string>
<string name="quick_action_take_audio_note">Запись аудио⁣</string>
<string name="quick_action_take_video_note">Записать видео</string>
<string name="quick_action_take_photo_note">Фотозаметка</string>
<string name="quick_action_add_osm_bug">OSM-заметка</string>
@ -86,7 +86,7 @@
<string name="shared_string_visible">Видимые</string>
<string name="restore_purchases">Восстановить покупки</string>
<string name="fonts_header">Шрифты карты</string>
<string name="analyze_on_map">Посмотреть на карте</string>
<string name="analyze_on_map">Анализ на карте</string>
<string name="nautical_maps">Морские карты</string>
<string name="download_depth_countours">Контуры морских глубин</string>
<string name="index_item_depth_contours_osmand_ext">Контуры морских глубин</string>
@ -115,7 +115,7 @@
<string name="routing_attr_driving_style_balance_name">Сбалансированный</string>
<string name="routing_attr_driving_style_safety_name">Предпочитать переулки</string>
<string name="relief_smoothness_factor_descr">Выберите предпочтительный рельеф.</string>
<string name="shared_string_slope">Склон</string>
<string name="shared_string_slope">Уклон</string>
<string name="add_new_folder">Добавить новую папку</string>
<string name="points_delete_multiple_succesful">Точки удалены.</string>
<string name="points_delete_multiple">Вы уверены, что хотите удалить %1$d точки\?</string>
@ -171,7 +171,7 @@
<string name="quick_action_map_source_action">Добавить источник карты</string>
<string name="quick_action_map_source_switch">Источник карты изменён на «%s».</string>
<string name="quick_action_btn_tutorial_descr">Удерживайте кнопку для перемещения её по экрану.</string>
<string name="rendering_attr_depthContours_description">Показывать контуры и точки глубины.</string>
<string name="rendering_attr_depthContours_description">Показывать контуры и точки глубин.</string>
<string name="rendering_attr_depthContours_name">Контуры морских глубин</string>
<string name="rendering_attr_contourDensity_description">Частота горизонталей</string>
<string name="rendering_attr_contourDensity_name">Частота горизонталей</string>
@ -233,7 +233,7 @@
<string name="save_filter">Сохранить фильтр</string>
<string name="delete_filter">Удалить фильтр</string>
<string name="new_filter">Новый фильтр</string>
<string name="change_markers_position">Изменить позицию</string>
<string name="change_markers_position">Изменение позиции</string>
<string name="current_track">Текущий путь</string>
<string name="use_osm_live_routing">Навигация OsmAnd Live</string>
<string name="map_widget_battery">Уровень заряда батареи</string>
@ -317,18 +317,18 @@
<string name="recording_open_external_player">Открыть внешний проигрыватель</string>
<string name="recording_delete_confirm">Удалить эту запись?</string>
<string name="recording_unavailable">недоступно</string>
<string name="recording_context_menu_arecord">Аудиозаметка</string>
<string name="recording_context_menu_vrecord">Видеозаметка</string>
<string name="layer_recordings">Слой аудиозаписей</string>
<string name="recording_can_not_be_played">Запись не может быть воспроизведена.</string>
<string name="recording_context_menu_arecord">Запись аудио</string>
<string name="recording_context_menu_vrecord">Запись видео</string>
<string name="layer_recordings">Слой медиазаписей</string>
<string name="recording_can_not_be_played">Не удаётся воспроизвести запись.</string>
<string name="recording_context_menu_delete">Удалить запись</string>
<string name="recording_context_menu_play">Проиграть</string>
<string name="recording_description">Запись %1$s %3$s %2$s</string>
<string name="recording_default_name">Запись</string>
<string name="map_widget_av_notes">Аудиозаметки</string>
<string name="map_widget_av_notes">Медиазаметки</string>
<string name="osmand_srtm_short_description_80_chars">OsmAnd-плагин для линий высот</string>
<string name="map_widget_distancemeasurement">Измерение расстояний</string>
<string name="audionotes_location_not_defined">Нажмите «Использовать местоположение…» чтобы добавить заметку к данному местоположению.</string>
<string name="audionotes_location_not_defined">Нажмите «Использовать местоположение…» для добавления заметки к месту.</string>
<string name="map_widget_audionotes">Аудиозаметки</string>
<string name="audionotes_plugin_description">Создавайте аудио-, видео- и фотозаметки в поездке, используя виджет или контекстное меню.</string>
<string name="audionotes_plugin_name">Аудио/видеозаметки</string>
@ -368,9 +368,9 @@
<string name="map_online_data_descr">Использовать онлайн-карты (загрузка и кеширование на SD-карте).</string>
<string name="shared_string_online_maps">Онлайн-карты</string>
<string name="online_map_settings_descr">Выберите источник онлайн или кешированных растровых карт.</string>
<string name="osmand_rastermaps_plugin_description">Доступ ко множеству онлайн-карт (т. н. тайловых или растровых): от встроенных OSM (как Mapnik), до спутниковых снимков и слоёв специального назначения, таких как карты погоды, климатические, геологические карты, затенения рельефа и др.
<string name="osmand_rastermaps_plugin_description">Доступ ко множеству онлайн-карт (т.н. тайловых или растровых): от встроенных OSM (как Mapnik) до спутниковых снимков и слоёв специального назначения, таких как карты погоды, климатические, геологические карты, затенения рельефа и др.
\n
\n Любая из этих карт может быть использована в качестве базовой либо как наложение или подложка к другой базовой карте (например стандартной локальной карте OsmAnd). Некоторые элементы векторной карты OsmAnd можно скрыть в меню «Настройки карты».
\n Любая из этих карт может быть использована как основная или в качестве подложки к другой карте (например стандартной локальной карте OsmAnd). Некоторые элементы векторной карты OsmAnd можно скрыть в меню «Настройки карты».
\n
\n Карты можно загрузить непосредственно из интернета или подготовить для использования в автономном режиме (и вручную скопировать в папку данных OsmAnd) в виде базы данных sqlite, которая может быть создана с помощью различных инструментов подготовки карт сторонних производителей.</string>
<string name="osmand_background_plugin_description">Показывает настройки для включения фонового отслеживания и навигации путём периодического пробуждения устройства GPS (с выключенным экраном).</string>
@ -505,7 +505,7 @@
<string name="local_indexes_cat_tts">Голосовые подсказки (TTS)</string>
<string name="local_indexes_cat_voice">Голосовые подсказки (записанные)</string>
<string name="local_indexes_cat_poi">Данные POI</string>
<string name="ttsvoice">Голос TTS</string>
<string name="ttsvoice">TTS</string>
<string name="search_offline_clear_search">Новый поиск</string>
<string name="map_text_size_descr">Размер текста для названий на карте:</string>
<string name="map_text_size">Размер текста</string>
@ -518,14 +518,14 @@
<string name="gpx_option_reverse_route">Обратное направление трека</string>
<string name="gpx_option_destination_point">Использовать текущий пункт назначения</string>
<string name="gpx_option_from_start_point">Пройти весь путь</string>
<string name="switch_to_vector_map_to_see">Для этого региона доступны локальные векторные карты.
<string name="switch_to_vector_map_to_see">Для этого региона есть локальные векторные карты.
\n\t
\n\tДля их использования выберите в \"Меню\" → \"Настройка карты\" → \"Источник карты…\" → \"Векторные карты\".</string>
<string name="choose_audio_stream">Голосовые инструкции</string>
\n\tДля использования выберите их в качестве источника (Меню → Настройка карты → Источник карты → Локальные векторные карты).</string>
<string name="choose_audio_stream">Аудиоканал голосовых инструкций</string>
<string name="choose_audio_stream_descr">Выберите канал вывода голосовых подсказок.</string>
<string name="voice_stream_voice_call">Канал голосовых звонков (прерывает автомобильную Bluetooth стереосистему)</string>
<string name="voice_stream_notification">Канал уведомлений</string>
<string name="voice_stream_music">Канал медиа/навигации</string>
<string name="voice_stream_voice_call">Голосовые звонки (для прерывания автомобильной стереосистемы Bluetooth)</string>
<string name="voice_stream_notification">Уведомления</string>
<string name="voice_stream_music">Мультимедиа, навигация</string>
<string name="warning_tile_layer_not_downloadable">Приложение не может загрузить слой карты %1$s, переустановка может решить проблему.</string>
<string name="overlay_transparency_descr">Отрегулируйте прозрачность наложения.</string>
<string name="overlay_transparency">Прозрачность наложения</string>
@ -669,9 +669,9 @@
<string name="vector_data">Локальные векторные карты</string>
<string name="poi_context_menu_modify">Редактировать POI</string>
<string name="poi_context_menu_delete">Удалить POI</string>
<string name="rotate_map_compass_opt">По направлению компаса</string>
<string name="rotate_map_bearing_opt">По направлению движения</string>
<string name="rotate_map_none_opt">Не вращать (север сверху)</string>
<string name="rotate_map_compass_opt">по направлению компаса</string>
<string name="rotate_map_bearing_opt">по направлению движения</string>
<string name="rotate_map_none_opt">не вращать (север сверху)</string>
<string name="rotate_map_to_bearing_descr">Выравнивание карты:</string>
<string name="rotate_map_to_bearing">Ориентация карты</string>
<string name="show_route">Детали маршрута</string>
@ -726,13 +726,13 @@
<string name="sd_mounted_ro">Карта памяти доступна только для чтения.
\nТеперь можно только просматривать предварительно загруженную карту, а не загружать новые области.</string>
<string name="unzipping_file">Файл распаковывается…</string>
<string name="route_tr">Направо и прямо</string>
<string name="route_tshr">Резко направо и прямо</string>
<string name="route_tslr">Плавно направо и прямо</string>
<string name="route_tl">Налево и прямо</string>
<string name="route_tshl">Резко налево и прямо</string>
<string name="route_tsll">Плавно налево и прямо</string>
<string name="route_tu">Выполните разворот, затем прямо</string>
<string name="route_tr">Направо</string>
<string name="route_tshr">Резко направо</string>
<string name="route_tslr">Плавно направо</string>
<string name="route_tl">Налево</string>
<string name="route_tshl">Резко налево</string>
<string name="route_tsll">Плавно налево</string>
<string name="route_tu">Выполните разворот</string>
<string name="route_head">Двигайтесь прямо</string>
<string name="first_time_continue">Продолжить</string>
<string name="first_time_download">Загрузить детальные карты регионов</string>
@ -844,7 +844,7 @@
<string name="save_current_track">Сохранить текущий трек</string>
<string name="save_track_interval_descr">Укажите интервал фиксирования точек для записи трека во время навигации</string>
<string name="save_track_interval">Интервал записи во время навигации</string>
<string name="save_track_to_gpx_descrp">Во время навигации GPX треки будут автоматически сохранены в папку с треками.</string>
<string name="save_track_to_gpx_descrp">Во время навигации GPX-треки будут автоматически сохранены в папку с треками.</string>
<string name="save_track_to_gpx">Автозапись трека во время навигации</string>
<string name="update_tile">Обновить карту</string>
<string name="reload_tile">Обновить часть карты</string>
@ -864,7 +864,7 @@
<string name="map_view_3d">3D вид</string>
<string name="show_poi_over_map_description">Показать последние использованные POI на карте.</string>
<string name="show_poi_over_map">Показывать POI</string>
<string name="map_tile_source_descr">Выберите источник онлайн или кешированных тайлов карты</string>
<string name="map_tile_source_descr">Выберите источник онлайн- или кешированных тайлов карт.</string>
<string name="map_tile_source">Растровые карты</string>
<string name="map_source">Источник карты</string>
<string name="use_internet">Использовать интернет</string>
@ -901,7 +901,7 @@
<string name="search_address_building_option">Дом</string>
<string name="search_address_street_option">Пересечение улиц</string>
<string name="context_menu_item_update_map">Обновить карту</string>
<string name="context_menu_item_create_poi">Создать POI</string>
<string name="context_menu_item_create_poi">Добавление POI</string>
<string name="shared_string_yes">Да</string>
<string name="shared_string_cancel">Отмена</string>
<string name="shared_string_no">Нет</string>
@ -1160,10 +1160,10 @@
\n — подсказки полосы движения, отображение ограничения скорости, предварительно записанные и синтезированные голосовые подсказки
\n</string>
<string name="avoid_motorway">Без автомагистралей</string>
<string name="snap_to_road_descr">Привязываться к дорогам во время навигации.</string>
<string name="snap_to_road_descr">Привязывать позицию к дороге во время навигации.</string>
<string name="snap_to_road">Привязка к дороге</string>
<string name="intermediate_point_too_far">Промежуточный пункт %1$s слишком далеко от ближайшей дороги.</string>
<string name="arrived_at_intermediate_point">Достигнут промежуточный пункт</string>
<string name="arrived_at_intermediate_point">Вы прибыли в промежуточный пункт</string>
<string name="context_menu_item_intermediate_point">Промежуточный пункт</string>
<string name="map_widget_intermediate_distance">Промежуточный пункт</string>
<string name="ending_point_too_far">Конец маршрута слишком далеко от ближайшей дороги.</string>
@ -1179,9 +1179,9 @@
<string name="context_menu_item_last_intermediate_point">Последний промежуточный пункт</string>
<string name="context_menu_item_first_intermediate_point">Первый промежуточный пункт</string>
<string name="add_as_last_destination_point">Добавить последним промежуточным пунктом</string>
<string name="add_as_first_destination_point">Добавить первым промежуточным пунктом</string>
<string name="add_as_first_destination_point">Первый промежуточный пункт</string>
<string name="replace_destination_point">Заменить пункт назначения</string>
<string name="new_destination_point_dialog">Пункт назначения уже задан:</string>
<string name="new_destination_point_dialog">Вы уже задали пункт назначения</string>
<string name="target_point">Пункт %1$s</string>
<string name="shared_string_target_points">Точки маршрута</string>
<string name="intermediate_point">Промежуточный пункт %1$s</string>
@ -1201,11 +1201,11 @@
<string name="av_settings">Настройки аудио и видео </string>
<string name="intermediate_points_change_order">Изменить порядок</string>
<string name="recording_context_menu_show">Просмотр</string>
<string name="av_def_action_picture">Сделать фото</string>
<string name="av_def_action_picture">Снимок</string>
<string name="recording_context_menu_precord">Сделать фото</string>
<string name="dropbox_plugin_description">Синхронизация треков и медиазаметок с вашим аккаунтом Dropbox.</string>
<string name="dropbox_plugin_name">Плагин Dropbox</string>
<string name="osmand_srtm_long_description_1000_chars">Плагин обеспечивает наложение контурных линии и (рельефа) затемняющего слоя, которые будут отображаться поверх стандартных карт OsmAnd. Эта функция высоко оценится спортсменами, туристами, путешественниками и всеми, кто заинтересован в рельефной структуре ландшафта.
<string name="osmand_srtm_long_description_1000_chars">Плагин обеспечивает наложение контурных линии и затемняющего слоя (рельефа), которые будут отображаться поверх стандартных карт OsmAnd. Эту функцию оценят спортсмены, туристы, путешественники и все, для кого рельеф местности имеет значение.
\n
\nГлобальные данные (между 70° на севере и 70° на юге) основываются на измерениях SRTM (Shuttle Radar Topography Mission) и ASTER (Advanced Spaceborne Thermal Emission and Reflection Radiometer), инструментом визуализации Terra, флагманского спутника Земли системы наблюдения NASA. ASTER является результатом совместных усилий NASA, министерства экономики Японии, торговли и промышленности (METI), космических систем Японии (J-spacesystems).</string>
<string name="recording_photo_description">Фото %1$s %2$s</string>
@ -1227,7 +1227,7 @@
<string name="access_arrival_time">Время прибытия</string>
<string name="map_widget_gps_info">Информация GPS</string>
<string name="max_speed_none">нет</string>
<string name="layer_hillshade">Слой рельефа местности</string>
<string name="layer_hillshade">Слой затенения рельефа</string>
<string name="street_name">Название улицы</string>
<string name="hno">Номер дома</string>
<string name="download_using_mobile_internet">Нет соединения по Wi-Fi. Использовать текущее интернет-соединение для загрузки\?</string>
@ -1239,7 +1239,7 @@
<string name="select_address_activity">Укажите адрес</string>
<string name="favourites_list_activity">Выбор избранной</string>
<string name="local_openstreetmap_act_title">Модификации OSM</string>
<string name="av_def_action_choose">Выбирать</string>
<string name="av_def_action_choose">Выбрать</string>
<string name="osmand_play_title_30_chars">OsmAnd карты и навигация</string>
<string name="osmand_plus_play_title_30_chars">OsmAnd+ карты и навигация</string>
<string name="use_kalman_filter_compass_descr">Уменьшает «шум» компаса, но добавляет инерцию.</string>
@ -1258,7 +1258,7 @@
<string name="cancel_route">Отменить маршрут</string>
<string name="clear_destination">Очистить пункт назначения</string>
<string name="search_street_in_neighborhood_cities">Искать улицу в ближайших населённых пунктах</string>
<string name="intermediate_items_sort_by_distance">В порядке следования домов</string>
<string name="intermediate_items_sort_by_distance">Расположить в оптимальном порядке</string>
<string name="available_downloads_left">Доступно %1$d файлов для скачивания</string>
<string name="files_limit">осталось %1$d файлов</string>
<string name="wait_current_task_finished">Подождите, пока завершится текущая операция</string>
@ -1273,9 +1273,9 @@
<string name="plugin_distance_point_ele">высота</string>
<string name="local_osm_changes_backup_successful">OsmChange-файл создан за %1$s</string>
<string name="use_distance_measurement_help">* Нажмите, чтобы отметить точку.
\n* Удерживайте нажатие на карте, чтобы удалить предыдущую точку.
\n* Удерживайте нажатие на точке, чтобы просмотреть и добавить описание.
\n* Нажмите на виджет измерения, чтобы увидеть больше действий.</string>
\n* Нажмите и удерживайте карту, чтобы удалить предыдущую точку.
\n* Удерживайте точку для просмотра и добавления описания.
\n* Нажмите на виджет измерения для других действий.</string>
<string name="use_magnetic_sensor_descr">Использовать магнитный датчик вместо датчика ориентации.</string>
<string name="other_location">Другие</string>
<string name="local_indexes_cat_srtm">Контурные линии</string>
@ -1301,9 +1301,9 @@
<string name="speak_traffic_warnings">Дорожные предупреждения</string>
<string name="clear_intermediate_points">Очистить промежуточные пункты</string>
<string name="keep_intermediate_points">Оставить промежуточные пункты</string>
<string name="route_to">К:</string>
<string name="route_to">Назначение:</string>
<string name="route_via">Через:</string>
<string name="route_from">От:</string>
<string name="route_from">Отправление:</string>
<string name="new_directions_point_dialog">Промежуточные пункты уже заданы.</string>
<string name="speak_street_names">Названия улиц (TTS)</string>
<string name="speak_title">Объявлять…</string>
@ -1345,9 +1345,9 @@
<string name="animate_routing_route">Симуляция использования рассчитанного маршрута </string>
<string name="animate_routing_gpx">Симуляция использования трека GPX</string>
<string name="auto_zoom_none">Без автомасштаба</string>
<string name="auto_zoom_close">Ближний план</string>
<string name="auto_zoom_far">Средний план</string>
<string name="auto_zoom_farthest">Дальний план</string>
<string name="auto_zoom_close">К ближнему плану</string>
<string name="auto_zoom_far">К среднему плану</string>
<string name="auto_zoom_farthest">К дальнему плану</string>
<string name="routing_attr_avoid_motorway_description">Избегать автомагистралей</string>
<string name="routing_attr_avoid_motorway_name">Без автомагистралей</string>
<string name="routing_attr_prefer_motorway_name">Предпочитать автомагистрали</string>
@ -1358,7 +1358,7 @@
<string name="routing_attr_avoid_unpaved_description">Избегать грунтовых дорог</string>
<string name="routing_attr_avoid_ferries_name">Без паромов</string>
<string name="routing_attr_avoid_ferries_description">Исключить паромные переправы</string>
<string name="routing_attr_weight_name">Максимальная масса</string>
<string name="routing_attr_weight_name">Предельная масса</string>
<string name="routing_attr_weight_description">Укажите допустимый предел массы автомобиля для учёта при построении маршрута.</string>
<string name="map_widget_map_rendering">Отображение карты</string>
<string name="amenity_type_seamark">Навигационные знаки (водоёмы)</string>
@ -1386,7 +1386,7 @@
<string name="route_descr_select_destination">Задать пункт назначения</string>
<string name="route_preferences">Предпочтения маршрута</string>
<string name="route_info">Информация про маршрут</string>
<string name="keep_and_add_destination_point">Добавить как новый пункт назначения</string>
<string name="keep_and_add_destination_point">Добавить новым пунктом назначения</string>
<string name="use_displayed_track_for_navigation">Использовать показанный путь для навигации?</string>
<string name="calculate_osmand_route_without_internet">Рассчитать сегмент маршрута OsmAnd без интернета</string>
<string name="gpx_option_calculate_first_last_segment">Рассчитать маршрут OsmAnd для первого и последнего сегмента маршрута</string>
@ -1445,15 +1445,15 @@
<string name="voice_pref_title">Голос</string>
<string name="misc_pref_title">Разное</string>
<string name="localization_pref_title">Локализация</string>
<string name="interrupt_music_descr">Голосовые подсказки приостанавливают воспроизведение музыки.</string>
<string name="interrupt_music">Приостановить музыку</string>
<string name="interrupt_music_descr">Приостановка воспроизведения во время подсказок.</string>
<string name="interrupt_music">Прерывать музыку</string>
<string name="share_route_as_gpx">Поделиться маршрутом используя файл GPX</string>
<string name="navigation_intent_invalid">Неправильный формат: %s</string>
<string name="share_route_subject">Маршрут предоставленный через OsmAnd</string>
<string name="keep_informing_never">Только вручную (нажатием «стрелочки»)</string>
<string name="keep_informing_never">При нажатии на стрелку (вручную)</string>
<string name="keep_informing_descr">Повторять навигационные инструкции с регулярными интервалами.</string>
<string name="keep_informing">Повторять навигационные инструкции</string>
<string name="arrival_distance">Объявление прибытия</string>
<string name="arrival_distance">Объявление о прибытии</string>
<string name="arrival_distance_descr">Как скоро следует сообщать о прибытии?</string>
<string name="share_fav_subject">Места, отправленные в OsmAnd</string>
<string name="use_points_as_intermediates">Рассчитать маршрут между точками</string>
@ -1492,7 +1492,7 @@
<string name="gpx_selection_number_of_points"> %1$s точек</string>
<string name="gpx_selection_point">Точка %1$s</string>
<string name="gpx_selection_route_points">%1$s \nМаршрутных точек %2$s</string>
<string name="show_zoom_buttons_navigation_descr">Показывать кнопки изменения масштаба во время навигации.</string>
<string name="show_zoom_buttons_navigation_descr">Показывать кнопки масштаба во время навигации.</string>
<string name="show_zoom_buttons_navigation">Кнопки масштаба</string>
<string name="sort_by_distance">Сортировать по расстоянию</string>
<string name="sort_by_name">Сортировать по имени</string>
@ -1540,10 +1540,10 @@
<string name="continue_navigation">Продолжить навигацию</string>
<string name="pause_navigation">Приостановить навигацию</string>
<string name="rendering_attr_alpineHiking_description">Визуализация пути по шкале SAC.</string>
<string name="rendering_attr_hikingRoutesOSMC_description">Визуализация пути согласно трассам OSMC.</string>
<string name="arrival_distance_factor_early">Пораньше</string>
<string name="arrival_distance_factor_normally">Как обычно</string>
<string name="arrival_distance_factor_late">Попозже</string>
<string name="rendering_attr_hikingRoutesOSMC_description">Отрисовка дорог согласно трассам OSMC.</string>
<string name="arrival_distance_factor_early">Раннее</string>
<string name="arrival_distance_factor_normally">По умолчанию</string>
<string name="arrival_distance_factor_late">Позднее</string>
<string name="arrival_distance_factor_at_last">На последних метрах</string>
<string name="rendering_attr_alpineHiking_name">Пеший горный туризм по шкале (SAC)</string>
<string name="rendering_attr_hikingRoutesOSMC_name">Наложение туристических меток</string>
@ -1739,7 +1739,7 @@
<string name="shared_string_deselect_all">Отменить выбор всех</string>
<string name="shared_string_share">Поделиться</string>
<string name="shared_string_my_places">Мои места</string>
<string name="shared_string_my_favorites">Точки</string>
<string name="shared_string_my_favorites">Избранные</string>
<string name="shared_string_tracks">Треки</string>
<string name="shared_string_currently_recording_track">Текущий трек</string>
<string name="share_note">Поделиться заметкой</string>
@ -1780,7 +1780,7 @@
\n
\nВ случае активации этого вида, стиль карты меняется на «Зимний/лыжный», показывая все детали пейзажа так, как они выглядят зимой. Такой (зимний) вид может быть отменён либо путём деактивации здесь, либо если вы поменяете «Стиль карты» в меню «Настройки карты» на желаемый вид.</string>
<string name="current_route">Текущий маршрут</string>
<string name="welmode_download_maps">Скачать карты</string>
<string name="welmode_download_maps">Загрузка карт</string>
<string name="welcome_select_region">Для правильного отображения дорожных знаков и правил выберите свой регион вождения:</string>
<string name="welcome_header">Добро пожаловать</string>
<string name="mark_to_delete">Отметить для удаления</string>
@ -1816,7 +1816,7 @@
<string name="search_poi_category_hint">Напечатайте для поиска</string>
<string name="rendering_attr_hideHouseNumbers_name">Номера домов</string>
<string name="routing_attr_avoid_borders_description">Избегать перехода границы</string>
<string name="routing_attr_height_name">Максимальная высота</string>
<string name="routing_attr_height_name">Предельная высота</string>
<string name="routing_attr_height_description">Укажите высоту транспортного средства для учёта при построении маршрута.</string>
<string name="use_fast_recalculation">Умный пересчёт маршрута</string>
<string name="use_fast_recalculation_desc">Для больших маршрутов пересчитывать только начало.</string>
@ -1905,8 +1905,8 @@
<string name="shared_string_skip">Пропустить</string>
<string name="app_name_osmand">OsmAnd</string>
<string name="plugin_settings">Плагины</string>
<string name="offline_maps_and_navigation">Локальные карты
\nи Навигация</string>
<string name="offline_maps_and_navigation">Офлайн-карты
\nи навигация</string>
<string name="building_number">Номер дома</string>
<string name="rendering_attr_pisteGrooming_name">Тип лыжной трассы</string>
<string name="download_live_updates">Автообновления</string>
@ -2036,7 +2036,7 @@
<string name="update_now">Обновить сейчас</string>
<string name="missing_write_external_storage_permission">Приложение не имеет разрешения на использование SD-карты</string>
<string name="available_maps">Доступные карты</string>
<string name="starting_point">Начальный пункт</string>
<string name="starting_point">Пункт отправления</string>
<string name="shared_string_not_selected">Не выбрано</string>
<string name="rec_split_storage_size">Размер хранилища</string>
<string name="shared_string_sound">Звук</string>
@ -2065,7 +2065,7 @@
<string name="rec_split">Разбиение на клипы</string>
<string name="rec_split_title">Использовать разбиение на клипы</string>
<string name="rec_split_desc">Циклическая перезапись клипов при превышении заданного объёма хранилища.</string>
<string name="switch_start_finish">Поменять местами пункты отправления и назначения</string>
<string name="switch_start_finish">Поменять местами пункты отправления и назначения</string>
<string name="shared_string_remove">Удалить</string>
<string name="rendering_attr_hideUnderground_name">Подземные объекты</string>
<string name="data_is_not_available">Данные недоступны</string>
@ -2289,8 +2289,8 @@
<string name="shared_string_add_photos">Добавить фото</string>
<string name="shared_string_permissions">Разрешения</string>
<string name="online_photos">Онлайн-фото</string>
<string name="no_photos_descr">Здесь нет фотографий.</string>
<string name="mapillary_action_descr">Поделитесь вашим просмотром улиц через Mapillary.</string>
<string name="no_photos_descr">Здесь нет фото.</string>
<string name="mapillary_action_descr">Поделитесь своими уличными видами через Mapillary.</string>
<string name="mapillary_widget">Виджет Mapillary</string>
<string name="mapillary_widget_descr">Позволяет быстро внести свой вклад в Mapillary.</string>
<string name="mapillary_descr">Фото с улиц онлайн для каждого. Открывайте места, взаимодействуйте, запечатлейте весь мир.</string>
@ -2337,7 +2337,7 @@
<string name="measurement_tool">Измерить расстояние</string>
<string name="none_point_error">Добавьте хотя бы одну точку.</string>
<string name="mapillary_image">Фотография Mapillary</string>
<string name="improve_coverage_mapillary">Улучшить фотопокрытие через Mapillary</string>
<string name="improve_coverage_mapillary">Улучшить фотопокрытие в Mapillary</string>
<string name="hide_from_zoom_level">Скрыть, начиная с уровня масштабирования</string>
<string name="rendering_value_translucent_pink_name">Прозрачно-розовый</string>
<string name="lang_ber">Берберский</string>
@ -2380,7 +2380,7 @@
<string name="routing_attr_avoid_ice_roads_fords_description">Избегать ледовых дорог и бродов.</string>
<string name="my_location">Моё местоположение</string>
<string name="shared_string_finish">Финиш</string>
<string name="shared_string_sort">Сортировать</string>
<string name="shared_string_sort">Сортировка</string>
<string name="marker_save_as_track_descr">Экспорт маркеров в следующий файл GPX:</string>
<string name="shared_string_markers">Маркеры</string>
<string name="osn_modify_dialog_title">Изменить заметку</string>
@ -2392,7 +2392,7 @@
<string name="order_by">Критерий сортировки:</string>
<string name="marker_show_distance_descr">Выберите способ указания расстояния и направления до маркеров на карте:</string>
<string name="map_orientation_change_in_accordance_with_speed">Смена ориентации карты</string>
<string name="map_orientation_change_in_accordance_with_speed_descr">Выберите скорость, при которой переключается ориентация карты с «По направлению движения» на «По направлению компаса».</string>
<string name="map_orientation_change_in_accordance_with_speed_descr">Выберите скорость, при которой ориентация по направлению движения переключится на ориентацию по компасу.</string>
<string name="all_markers_moved_to_history">Все маркеры перемещены в историю</string>
<string name="marker_moved_to_history">Маркер перемещён в историю</string>
<string name="marker_moved_to_active">Маркер перемещён в действующие</string>
@ -2470,7 +2470,7 @@
<string name="tap_on_map_to_hide_interface">Полноэкранный режим</string>
<string name="mark_passed">Отметить пройденным</string>
<string name="import_track_desc">Файл %1$s не содержит путевых точек, импортировать его как трек?</string>
<string name="add_track_to_markers_descr">Выберите трек, чтобы добавить в маркеры его точки.</string>
<string name="add_track_to_markers_descr">Выберите трек, чтобы добавить в маркеры его точки.</string>
<string name="shared_string_gpx_waypoints">Трек путевых точек</string>
<string name="shared_string_right">Направо</string>
<string name="shared_string_left">Налево</string>
@ -2521,13 +2521,13 @@
\n• Запись собственного или отправка GPX трека и следование ему
\n</string>
<string name="osmand_extended_description_part3">Карта
\n• Отображает POI (точки интереса) около вас
\n• Адаптирует карту в направлении вашего движения (или компаса)
\n• Показывает, где вы находитесь и куда вы смотрите
\n• Делитесь своим расположением, чтобы друзья смогли найти вас
\n• Сохраняет ваши самые важные места в избранных
\n• Позволяет вам выбрать как отображать названия на карте: на английском, местным или с фонетическим написанием
\n• Отображает специальные онлайн-тайлы, спутниковые снимки (с Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью
\n• Отображает POI (точки интереса) вокруг вас
\n• Поворачивает карту по направлению движения (или компаса)
\n• Показывает вашу позицию и направление взгляда
\n• Делитесь вашим местоположением, чтобы вас могли найти друзья
\n• Сохраняет важные для вас места в избранных
\n• Позволяет выбрать способ отображения названий на карте: на английском, местное или фонетическое написание.
\n• Отображает специальные онлайн-тайлы, спутниковые снимки (Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью
\n</string>
<string name="osmand_extended_description_part4">Катание на лыжах
\n• OsmAnd-плагин лыжные карты позволяет видеть лыжные трассы с уровнем сложности и некоторой дополнительной информацией, как расположение подъёмников и других объектов.</string>
@ -2670,7 +2670,7 @@
<string name="shared_string_current">Текущий</string>
<string name="last_intermediate_dest_description">Добавляет промежуточную остановку</string>
<string name="first_intermediate_dest_description">Добавляет начальную остановку</string>
<string name="subsequent_dest_description">Перемещает пункт назначения и создаёт промежуточную точку</string>
<string name="subsequent_dest_description">Добавляет новый пункт назначения, делая выбранный ранее промежуточной точкой</string>
<string name="show_closed_notes">Показать закрытые заметки</string>
<string name="switch_osm_notes_visibility_desc">Показать/скрыть заметки OSM на карте.</string>
<string name="gpx_file_desc">GPX — подходит для экспорта в JOSM и другие OSM редакторы.</string>
@ -2683,7 +2683,7 @@
<string name="osm_notes">OSM-заметки</string>
<string name="tunnel_warning">Впереди туннель</string>
<string name="show_tunnels">Туннели</string>
<string name="make_as_start_point">Сделать отправной точкой</string>
<string name="make_as_start_point">Сделать пунктом отправления</string>
<string name="enter_the_file_name">Введите имя файла.</string>
<string name="map_import_error">Ошибка импорта карты</string>
<string name="map_imported_successfully">Карта импортирована</string>
@ -2764,7 +2764,7 @@
<string name="start_editing">Начать редактирование</string>
<string name="get_unlimited_access">Получить неограниченный доступ</string>
<string name="welcome_to_open_beta">Добро пожаловать на открытое бета-тестирование</string>
<string name="contour_lines_hillshade_maps">Карты горизонталей и карты с отмывкой рельефа</string>
<string name="contour_lines_hillshade_maps">Карты горизонталей и затенение рельефа</string>
<string name="download_wikipedia_description">Скачать статьи Википедии для %1$s, чтобы читать их в автономном режиме.</string>
<string name="download_wikipedia_label">Загрузка данных Википедии</string>
<string name="open_in_browser_wiki">Открыть статью в интернете</string>
@ -2788,7 +2788,7 @@
<string name="ski_map_render_descr">Для катания на лыжах. Выделяет горнолыжные трассы, подъёмники, трассы для беговых лыж и прочее. Меньше отвлекающих второстепенных объектов на карте.</string>
<string name="download_all">Скачать все</string>
<string name="light_rs_render_descr">Простой стиль для вождения. Мягкий ночной режим, контурные линии, контрастные дороги в оранжевом стиле, тусклые второстепенные объекты карты.</string>
<string name="topo_render_descr">Для пеших походов, трекинга и велосипедных прогулок на природе. Читабельный на открытом воздухе и при сложном освещении. Контрастные дороги и природные объекты, различные типы маршрутов, контурные линии с расширенными настройками, дополнительные детали. Функция «Качество дорожного покрытия» позволяет различать дороги с различным качеством поверхности. Нет ночного режима.</string>
<string name="topo_render_descr">Для пеших походов, трекинга и велопоездок на природе. Хорошо читается при сложном освещении. Контрастные дороги и природные объекты, различные типы маршрутов, контурные линии с расширенными настройками, дополнительные детали. Параметр «Дорожное покрытие» позволяет различать поверхность и качество дорог. Ночной режим отсутствует.</string>
<string name="mapnik_render_descr">Старый стиль по умолчанию «Mapnik». Похожие цвета на «Mapnik».</string>
<string name="default_render_descr">Стиль общего назначения. Густонаселённые города показаны упрощённо. Выделяет контурные линии, маршруты, качество поверхности, ограничения доступа, дорожные щиты, визуализация пешеходных маршрутов по шкале SAC, объекты спортивных сплавов.</string>
<string name="open_wikipedia_link_online">Открыть ссылку Википедии в онлайн</string>
@ -2796,13 +2796,13 @@
<string name="read_wikipedia_offline_description">Получите подписку на OsmAnd Live, чтобы читать статьи в Википедии и Викигиде в автономном режиме.</string>
<string name="how_to_open_link">Как открыть ссылку?</string>
<string name="read_wikipedia_offline">Читать Википедию в автономном режиме</string>
<string name="touring_view_render_descr">Туристический стиль с высоким контрастом и максимальной детализацией. Включает все функции стиля OsmAnd по умолчанию, также отображая как можно больше деталей, в частности дороги, тропы и другие пути для передвижения. Чёткое различие между типами дорог, как во многих туристических атласах. Подходит для дневного, ночного и уличного использования.</string>
<string name="touring_view_render_descr">Туристический стиль с высоким контрастом и максимальной детализацией. Включает все функции стиля OsmAnd по умолчанию, отображая максимальное количество деталей, в частности дороги, тропы и другие пути передвижения. Чёткое различие между типами дорог (как в туристических атласах). Подходит для использования днём, ночью и при ярком освещении.</string>
<string name="shared_string_bookmark">Сохранить</string>
<string name="off_road_render_descr">Для езды по бездорожью, основано на топографическом стиле (англ. «Topo»), можно использовать с зелёными спутниковыми снимками в качестве подложки. Уменьшенная толщина основных дорог, увеличенная толщина путей, дорожек, велосипедных и других маршрутов.</string>
<string name="unirs_render_descr">Модификация стиля по умолчанию для увеличения контраста пешеходных и велосипедных дорог. Использует старые цвета Mapnik.</string>
<string name="get_osmand_live">Получите OsmAnd Live, чтобы разблокировать все функции: ежедневные обновления карт с неограниченной загрузкой, все платные и бесплатные плагины, Википедия, Викигид и многое другое.</string>
<string name="access_intermediate_arrival_time">Промежуточное время прибытия</string>
<string name="map_widget_intermediate_time">Промежуточное время</string>
<string name="access_intermediate_arrival_time">Прибытие в промежуточный пункт</string>
<string name="map_widget_intermediate_time">Прибытие в промежуточный пункт</string>
<string name="quick_action_edit_actions">Редактировать действие</string>
<string name="error_notification_desc">Пожалуйста, пришлите скриншот этого уведомления на support@osmand.net</string>
<string name="coord_input_edit_point">Редактировать точку</string>
@ -2855,7 +2855,7 @@
<string name="ask_for_location_permission">Для продолжения дайте OsmAnd разрешение на определение местоположения.</string>
<string name="rendering_value_black_name">Чёрный</string>
<string name="search_street">Поиск улицы</string>
<string name="start_search_from_city">Сначала выберите город/населённый пункт/местность</string>
<string name="start_search_from_city">Укажите город/место/район</string>
<string name="shared_string_restore">Восстановить</string>
<string name="keep_passed_markers_descr">Маркеры, добавленные как группа избранных или путевых точек GPX и отмеченные как пройденные, останутся на карте. Если группа не активна, маркеры исчезнут с карты.</string>
<string name="keep_passed_markers">Оставить пройденные маркеры на карте</string>
@ -2891,7 +2891,7 @@
<string name="points_of_interests">Точки интереса (POI)</string>
<string name="waiting_for_route_calculation">Расчёт маршрута…</string>
<string name="app_mode_public_transport">Общественный транспорт</string>
<string name="avoid_roads_descr">Выберите дорогу на карте или из списка ниже, которую вы хотите избежать во время навигации:</string>
<string name="avoid_roads_descr">Выберите на карте или в списке ниже дорогу, которой хотите избежать при навигации:</string>
<string name="simulate_navigation">Моделировать навигацию</string>
<string name="choose_track_file_to_follow">Выберите файл трека для следования</string>
<string name="voice_announcements">Голосовые подсказки</string>
@ -2923,7 +2923,7 @@
<string name="quick_action_gpx_tracks_hide">Скрыть треки</string>
<string name="quick_action_gpx_tracks_show">Показать треки</string>
<string name="time_of_day">Время суток</string>
<string name="step_by_step">Поворот за поворотом</string>
<string name="step_by_step">По шагам</string>
<string name="routeInfo_road_types_name">Типы дорог</string>
<string name="quick_action_show_hide_gpx_tracks_descr">Переключатель, чтобы показать или скрыть выбранные треки на карте.</string>
<string name="by_transport_type">На %1$s</string>
@ -2987,7 +2987,7 @@
<string name="rendering_attr_highway_class_steps_name">Ступеньки</string>
<string name="rendering_attr_highway_class_path_name">Тропа</string>
<string name="rendering_attr_highway_class_cycleway_name">Велодорожка</string>
<string name="rendering_attr_undefined_name">Неопределённая</string>
<string name="rendering_attr_undefined_name">Не определено</string>
<string name="public_transport_warning_descr_blog">Узнайте больше о маршрутизации OsmAnd в нашем блоге.</string>
<string name="public_transport_warning_title">Навигация на общественном транспорте в настоящее время проходит бета-тестирование, возможны ошибки и неточности.</string>
<string name="add_intermediate">Добавить промежуточную точку</string>
@ -3066,7 +3066,7 @@
<string name="app_mode_subway">Метро</string>
<string name="app_mode_horse">Лошадь</string>
<string name="app_mode_helicopter">Вертолёт</string>
<string name="osmand_routing_promo">Вы можете добавить собственную модифицированную версию routing.xml в ..osmand/routing</string>
<string name="osmand_routing_promo">Вы можете добавить свою модифицированную версию файла routing.xml в ..osmand/routing</string>
<string name="select_icon_profile_dialog_title">Выберите значок</string>
<string name="routing_profile_ski">Лыжи</string>
<string name="profile_type_descr_string">Тип: %s</string>
@ -3101,7 +3101,7 @@
<string name="zoom_by_wunderlinq">Использовать WunderLINQ для контроля</string>
<string name="shared_string_icon">Значок</string>
<string name="collected_data">Собранные данные</string>
<string name="press_again_to_change_the_map_orientation">Нажмите ещё раз, чтобы изменить ориентацию карты</string>
<string name="press_again_to_change_the_map_orientation">Нажмите ещё раз для смены ориентации карты</string>
<string name="last_launch_crashed">Последний запуск OsmAnd завершился ошибкой. Пожалуйста, помогите нам улучшить OsmAnd, отправив нам отчёт об ошибке.</string>
<string name="settings_routing_mode_string">Режим: %s</string>
<string name="settings_derived_routing_mode_string">Режим пользователя, полученный из: %s</string>
@ -3158,7 +3158,7 @@
<string name="shared_string_crash">Сбой</string>
<string name="app_mode_offroad">Внедорожник</string>
<string name="edit_profile_setup_map_subtitle">Выбор настроек карты для профиля</string>
<string name="edit_profile_screen_options_subtitle">Выбор настроек экрана для профиля</string>
<string name="edit_profile_screen_options_subtitle">Настройка элементов экрана для профиля</string>
<string name="edit_profile_nav_settings_subtitle">Выбор настроек навигации для профиля</string>
<string name="routing_attr_max_num_changes_description">Выбор верхней границы изменений</string>
<string name="turn_screen_on_sensor">Использовать бесконтактный датчик (сенсорный выключатель)</string>
@ -3233,11 +3233,11 @@
<string name="map_during_navigation">Карта во время навигации</string>
<string name="vehicle_parameters_descr">Скорость движения, размеры, масса транспортного средства</string>
<string name="vehicle_parameters">Параметры транспортного средства</string>
<string name="voice_announces_info">Голосовые оповещения происходят только во время навигации.</string>
<string name="voice_announces_info">Голосовые инструкции работают только во время навигации.</string>
<string name="voice_announces_descr">Навигационные инструкции и объявления</string>
<string name="voice_announces">Голосовые подсказки</string>
<string name="screen_alerts">Экранные оповещения</string>
<string name="route_parameters_descr">Настройка параметров маршрута</string>
<string name="route_parameters_descr">Настройки маршрутизации</string>
<string name="route_parameters">Параметры маршрута</string>
<string name="logcat_buffer">Буфер Logcat</string>
<string name="plugins_settings">Настройки плагинов</string>
@ -3319,7 +3319,7 @@
<string name="overwrite_profile_q">«%1$s» уже существует. Перезаписать\?</string>
<string name="export_profile_failed">Не удалось экспортировать профиль.</string>
<string name="profile_import">Импорт профиля</string>
<string name="profile_import_descr">Чтобы добавить профиль, откройте его с помощью OsmAnd.</string>
<string name="profile_import_descr">Для добавления профиля откройте файл профиля с помощью OsmAnd.</string>
<string name="file_imported_successfully">%1$s импортирован.</string>
<string name="rendering_value_white_name">Белый</string>
<string name="tts_initialization_error">Невозможно запустить механизм преобразования текста в речь.</string>
@ -3366,7 +3366,7 @@
<string name="routing_attr_allow_skating_only_description">Маршруты, подготовленные для фристайла или катания только на коньках без классических треков.</string>
<string name="routing_attr_allow_classic_only_name">Разрешить только классические маршруты</string>
<string name="routing_attr_allow_classic_only_description">Маршруты, подготовленные только для классического стиля без конькобежных трасс. Сюда входят маршруты, подготовленные небольшим снегоходом с более свободной лыжнёй и трассами, подготовленные вручную лыжниками.</string>
<string name="routing_attr_difficulty_preference_description">Предпочитать маршруты заданной сложности, хотя прокладка маршрута по более сложным или лёгким трассам всё же возможна, если они короче.</string>
<string name="routing_attr_difficulty_preference_description">Предпочтительный уровень сложности маршрутов. Более сложные или лёгкие трассы могут использоваться, если они короче.</string>
<string name="turn_screen_on_router">Включать на повороте</string>
<string name="rendering_attr_highway_class_track_grade1_name">Класс 1</string>
<string name="rendering_attr_highway_class_track_grade2_name">Класс 2</string>
@ -3390,7 +3390,7 @@
<string name="rendering_attr_piste_difficulty_expert_name">Эксперт</string>
<string name="rendering_attr_piste_difficulty_freeride_name">Фрирайд</string>
<string name="rendering_attr_piste_difficulty_extreme_name">Экстрим</string>
<string name="rendering_attr_piste_difficulty_undefined_name">Неопределённо</string>
<string name="rendering_attr_piste_difficulty_undefined_name">Неопределённая</string>
<string name="rendering_attr_piste_difficulty_aerialway_name">Канатная дорога</string>
<string name="rendering_attr_piste_difficulty_connection_name">Соединение</string>
<string name="simulate_your_location_gpx_descr">Симулировать свою позицию используя записанный GPX трек.</string>
@ -3553,9 +3553,9 @@
<string name="select_distance_route_will_recalc">Отклонение, при котором маршрут будет пересчитан.</string>
<string name="shared_string_legend">Легенда</string>
<string name="search_offline_geo_error">Невозможно разобрать геоссылку «%s».</string>
<string name="hillshade_download_description">Для отображения затенения рельефа на карте необходимы дополнительные карты.</string>
<string name="hillshade_download_description">Для отображения затенения рельефа требуются дополнительные карты.</string>
<string name="shared_string_min">Мин.</string>
<string name="terrain_empty_state_text">Отображение затенения рельефа или карты уклонов. Подробнее об этих типах карт вы можете прочитать на нашем сайте.</string>
<string name="terrain_empty_state_text">Способы отображения рельефа местности: посредством теней (затенение рельефа) или цветов (карта уклонов). Подробнее об этих типах карт вы можете прочитать на нашем сайте.</string>
<string name="shared_string_transparency">Прозрачность</string>
<string name="shared_string_zoom_levels">Уровни масштаба</string>
<string name="recalculate_route_in_deviation">Пересчитывать маршрут в случае отклонения</string>
@ -3580,14 +3580,14 @@
<string name="monitoring_min_speed_descr_remark">Примечание: проверка скорости &gt; 0: большинство модулей GPS сообщают значение скорости только в том случае, если алгоритм определяет, что вы движетесь, и ничего, если вы не перемещаетесь. Следовательно, использование параметра &gt; 0 в этом фильтре в некотором смысле приводит к обнаружению факта перемещения модуля GPS. Но даже если мы не производим данную фильтрацию во время записи, то всё равно эта функция используется при анализе GPX для определения скорректированного расстояния, то есть значение, отображаемое в этом поле, является расстоянием, записанным во время движения.</string>
<string name="multimedia_rec_split_title">Разделение записи</string>
<string name="live_monitoring_adress_descr">Укажите веб-адрес со следующими параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}.</string>
<string name="monitoring_min_accuracy_descr">В этом случае будут записываться только точки, измеренные с минимальной точностью (в метрах/футах согласно настройкам устройства). Точность — это близость измерений к истинному местоположению и не имеет прямого отношения к точности, подразумевающейся под разбросом повторных замеров.</string>
<string name="monitoring_min_accuracy_descr">"Будут записываться только точки, измеренные с указанием минимальной точности (в метрах/футах —д зависит от настроек системы). Точность — это близость измерений к истинному положению, и она не связана напрямую с точностью, которая представляет собой разброс повторных измерений."</string>
<string name="monitoring_min_speed_descr_recommendation">Рекомендация: попробуйте сначала воспользоваться детектором движения через фильтр минимального смещения (B), что может дать лучшие результаты и вы потеряете меньше данных. Если треки остаются шумными на низких скоростях, попробуйте использовать ненулевые значения. Обратите внимание, что некоторые измерения могут вообще не указывать значения скорости (некоторые сетевые методы), и в этом случае ничего не будет записываться.</string>
<string name="slope_description">Уклон использует цвета для визуализации крутизны рельефа.</string>
<string name="slope_description">Для визуализации крутизны рельефа используются цвета.</string>
<string name="slope_read_more">Подробнее об уклонах можно прочитать в %1$s.</string>
<string name="shared_string_hillshade">Затенение рельефа</string>
<string name="hillshade_description">Затенение рельефа использует тёмные оттенки для отображения склонов, вершин и низменностей.</string>
<string name="slope_download_description">Для отображения склонов на карте необходимы дополнительные карты.</string>
<string name="download_slope_maps">Уклоны</string>
<string name="hillshade_description">Для отображения склонов, вершин и низменностей используются тёмные тени.</string>
<string name="slope_download_description">Для отображения уклонов требуются дополнительные карты.</string>
<string name="download_slope_maps">Карта уклонов</string>
<string name="replace_point_descr">Заменить этой точкой другую.</string>
<string name="changes_applied_to_profile">Изменения применены к профилю «%1$s».</string>
<string name="settings_item_read_error">Невозможно прочитать из «%1$s».</string>
@ -3803,7 +3803,7 @@
<string name="add_to_a_track">Добавить к треку</string>
<string name="set_working_days_to_continue">Для продолжения задайте рабочие дни</string>
<string name="route_between_points">Маршрут между точками</string>
<string name="plan_a_route">Составить маршрут</string>
<string name="plan_a_route">Составление маршрута</string>
<string name="gpx_split_interval_none_descr">Выберите способ разбиения: по времени или по расстоянию.</string>
<string name="gpx_split_interval_descr">Интервал между метками расстояния или времени на треке.</string>
<string name="shared_string_custom">Своё</string>
@ -3883,17 +3883,17 @@
<string name="shared_string_is_saved">сохранен</string>
<string name="one_point_error">Добавьте хотя бы две точки.</string>
<string name="shared_string_redo">ПОВТОРИТЬ</string>
<string name="release_3_8">• Обновлённый режим планирования маршрута позволяет использовать разные типы навигации для каждого сегмента и прикрепляет любой трек к дорогам
<string name="release_3_8">• Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам
\n
\n • Новые параметры внешнего вида для треков: можно выбрать цвет, толщину, включите стрелки направления и отметки начала/окончания
\n • Новые настройки вида треков: выбор цвета и толщины линии, указатели направления, метки начала и конца маршрута
\n
\n • Улучшена видимость велосипедных узлов
\n • Повышенная видимость велосипедных узлов
\n
\n • Контекстное меню для треков с основной информацией
\n • Контекстное меню с основной информацией для треков
\n
\n • Улучшенные алгоритмы поиска
\n
\n • Улучшены параметры следования по треку в навигации
\n • Улучшенные настройки следования по треку в Навигации
\n
\n • Исправлены проблемы с импортом/экспортом настроек профиля
\n
@ -3901,4 +3901,5 @@
<string name="sort_last_modified">Последнее изменение</string>
<string name="sort_name_descending">Имя: Я - А</string>
<string name="sort_name_ascending">Имя: А - Я</string>
<string name="start_finish_icons">Значки старта и финиша</string>
</resources>

View file

@ -3840,4 +3840,5 @@
<string name="poi_recycling_small_electrical_appliances">Eletrodomèsticos minores</string>
<string name="poi_departures_board">Tabellone de sas tzucadas</string>
<string name="poi_drinking_water_refill">Ricàrriga de abba potàbile</string>
<string name="poi_fuel_lng">GNL (LNG)</string>
</resources>

View file

@ -3903,4 +3903,5 @@
<string name="sort_last_modified">Ùrtima modìfica</string>
<string name="sort_name_descending">Nùmene: Z A</string>
<string name="sort_name_ascending">Nùmene: A Z</string>
<string name="start_finish_icons">Iconas de incumintzu/fine</string>
</resources>

View file

@ -3274,9 +3274,9 @@
<string name="poi_denomination_mahayana">Mahájana</string>
<string name="poi_deadlock">Zamrznutí</string>
<string name="poi_information_trail_blaze">Turistická/trasová značka</string>
<string name="poi_memorial_koshinto"/>
<string name="poi_memorial_jizo"/>
<string name="poi_memorial_prasat"/>
<string name="poi_memorial_koshinto">Ko-Shintō</string>
<string name="poi_memorial_jizo">Jizō</string>
<string name="poi_memorial_prasat">Prasat</string>
<string name="poi_denomination_assemblies_of_god">Apoštolská cirkev</string>
<string name="poi_health_specialty_radiotheraphy_yes">Radiačná onkológia</string>
<string name="poi_hazard">Nebezpečenstvo</string>
@ -3567,4 +3567,89 @@
<string name="poi_traffic_signals_arrow">Šípka</string>
<string name="poi_traffic_signals_vibration">Vibrácie</string>
<string name="poi_fire_hydrant_pressure_filter">Tlak</string>
<string name="poi_fuel_lng">Skvapalnený zemný plyn</string>
<string name="poi_nuts">Obchod s orechmi</string>
<string name="poi_beehive">Včelí úľ</string>
<string name="poi_departures_board_timetable">Cestovný poriadok</string>
<string name="poi_departures_board_realtime">Odjazdy v reálnom čase</string>
<string name="poi_departures_board_delay">Intervaly</string>
<string name="poi_departures_board_yes">Áno</string>
<string name="poi_departures_board_no">Tabuľa odjazdov: nie</string>
<string name="poi_elevator">Výťah</string>
<string name="poi_city_block">Mestský blok</string>
<string name="poi_borough">Mestský obvod</string>
<string name="poi_traffic_signals_arrow_no">Šípka: nie</string>
<string name="poi_traffic_signals_arrow_yes">Nie</string>
<string name="poi_traffic_signals_vibration_yes">Áno</string>
<string name="poi_traffic_signals_vibration_no">Vibrovanie: nie</string>
<string name="poi_cash_withdrawal_foreign_cards">Výber hotovosti: cudzie karty</string>
<string name="poi_cash_withdrawal_purchase_minimum">Výber hotovosti: minimálny nákup</string>
<string name="poi_cash_withdrawal_fee_no">Poplatok za výber hotovosti: nie</string>
<string name="poi_cash_withdrawal_fee_yes">Poplatok za výber hotovosti: áno</string>
<string name="poi_cash_withdrawal_purchase_required_no">Výber hotovosti: nie je vyžadovaný nákup</string>
<string name="poi_cash_withdrawal_purchase_required_yes">Výber hotovosti: vyžadovaný nákup</string>
<string name="poi_cash_withdrawal_currency">Mena výberu hotovosti</string>
<string name="poi_cash_withdrawal_limit">Limit výberu hotovosti</string>
<string name="poi_cash_withdrawal_type_self_checkout">Typ výberu hotovosti: samoobslužný výber</string>
<string name="poi_cash_withdrawal_type_checkout">Typ výberu hotovosti: pri pokladni</string>
<string name="poi_cash_withdrawal_operator">Operátor výberu hotovosti</string>
<string name="poi_cash_withdrawal">Výber hotovosti</string>
<string name="poi_cash_withdrawal_yes">Výber hotovosti: áno</string>
<string name="poi_climbing_mixed_no">Zmiešané: nie</string>
<string name="poi_climbing_mixed_yes">Zmiešané: áno</string>
<string name="poi_climbing_ice_no">Ľad: nie</string>
<string name="poi_climbing_ice_yes">Ľad: áno</string>
<string name="poi_water_place_durability_emergency">Trvanlivosť vodného zdroja: núdzový</string>
<string name="poi_water_place_durability_durable">Trvanlivosť vodného zdroja: trvalý</string>
<string name="poi_office_midwife">Ordinácia pôrodnej asistentky</string>
<string name="poi_office_nursing_service">Opatrovateľská služba</string>
<string name="poi_office_psychologist">Ordinácia psychológa</string>
<string name="poi_office_healer">Ordinácia liečiteľa</string>
<string name="poi_office_therapist">Ordinácia terapeuta</string>
<string name="poi_office_physician">Ordinácia lekára</string>
<string name="poi_health_service_test_no">Zdravotná služba: testovanie: nie</string>
<string name="poi_health_service_test_yes">Zdravotná služba: testovanie: áno</string>
<string name="poi_health_service_support_no">Zdravotná služba: podpora: nie</string>
<string name="poi_health_service_support_yes">Zdravotná služba: podpora: áno</string>
<string name="poi_health_service_prevention_vaccination_no">Zdravotná služba: očkovanie: nie</string>
<string name="poi_health_service_prevention_vaccination_yes">Zdravotná služba: očkovanie: áno</string>
<string name="poi_health_service_prevention_no">Zdravotná služba: prevencia: nie</string>
<string name="poi_health_service_prevention_yes">Zdravotná služba: prevencia: áno</string>
<string name="poi_health_service_child_care_no">Zdravotná služba: starostlivosť o deti: nie</string>
<string name="poi_health_service_child_care_yes">Zdravotná služba: starostlivosť o deti: áno</string>
<string name="poi_health_service_examination_no">Zdravotná služba: vyšetrenie: nie</string>
<string name="poi_health_service_examination_yes">Zdravotná služba: vyšetrenie: áno</string>
<string name="poi_health_service_counselling_no">Zdravotná služba: poradenstvo: nie</string>
<string name="poi_health_service_counselling_yes">Zdravotná služba: poradenstvo: áno</string>
<string name="poi_health_service_nursing_yes">Zdravotná služba: opatrovateľstvo: áno</string>
<string name="poi_health_service_nursing_no">Zdravotná služba: opatrovateľstvo: nie</string>
<string name="poi_health_specialty_behavior_yes">Správanie</string>
<string name="poi_health_specialty_sports_medicine_yes">Športová medicína</string>
<string name="poi_recycling_small_electrical_appliances">Malé elektrické spotrebiče</string>
<string name="poi_departures_board">Tabuľa odjazdov/cestovný poriadok</string>
<string name="poi_drinking_water_refill">Doplnenie pitnej vody</string>
<string name="poi_osmand_fire_hydrant_pressure_suction">Nasávanie</string>
<string name="poi_osmand_fire_hydrant_pressure_pressurized">Pod tlakom</string>
<string name="poi_fire_hydrant_style_water_source_groundwater">Spodná voda</string>
<string name="poi_fire_hydrant_type_pipe">Potrubie</string>
<string name="poi_drinking_water_refill_network">Sieť doplnenia pitnej vody</string>
<string name="poi_drinking_water_refill_no">Doplnenie pitnej vody: nie</string>
<string name="poi_drinking_water_refill_yes">Áno</string>
<string name="poi_seamark_obstruction">Prekážka</string>
<string name="poi_seamark_water_level_below_mwl">Výška vody: pod strednou hladinou</string>
<string name="poi_seamark_water_level_above_mwl">Výška vody: nad strednou hladinou</string>
<string name="poi_seamark_water_level_covers">Výška vody: prekrýva</string>
<string name="poi_seamark_water_level_dry">Výška vody: suché</string>
<string name="poi_seamark_water_level_submerged">Výška vody: ponorené</string>
<string name="poi_seamark_water_level_part_submerged">Výška vody: čiastočne ponorené</string>
<string name="poi_tactile_paving_incorrect">Nesprávne</string>
<string name="poi_tactile_paving_primitive">Primitívne</string>
<string name="poi_tactile_paving_contrasted">Kontrastné</string>
<string name="poi_traffic_signals_sound_locate">Len keď je povolená chôdza</string>
<string name="poi_video_no">Nie</string>
<string name="poi_video_yes">Áno</string>
<string name="poi_booth">Typ búdky</string>
<string name="poi_covered_booth">Búdka</string>
<string name="poi_sms_no">Nie</string>
<string name="poi_sms_yes">Áno</string>
</resources>

View file

@ -3902,4 +3902,5 @@
<string name="sort_last_modified">Naposledy zmenené</string>
<string name="sort_name_descending">Názov: Z A</string>
<string name="sort_name_ascending">Názov: A Z</string>
<string name="start_finish_icons">Ikony štartu/cieľa</string>
</resources>

View file

@ -45,7 +45,7 @@
<string name="poi_user_defined_other">Kullanıcı tanımlı</string>
<string name="poi_palaeontological_site">Paleontolojik alan</string>
<string name="poi_bakery">Fırın</string>
<string name="poi_alcohol">Likör dükkanı</string>
<string name="poi_alcohol">İçki dükkanı</string>
<string name="poi_cheese">Peynir dükkanı</string>
<string name="poi_chocolate">Çikolata dükkanı</string>
<string name="poi_coffee">Kahve dükkanı</string>
@ -362,7 +362,7 @@
<string name="poi_recycling_diapers">Çocuk bezi</string>
<string name="poi_recycling_car_batteries">Araba aküsü</string>
<string name="poi_recycling_cars">Arabalar</string>
<string name="poi_recycling_bicycles">Bisiklet</string>
<string name="poi_recycling_bicycles">Bisikletler</string>
<string name="poi_landfill">Depolama</string>
<string name="poi_waste_disposal">Çöp bertaraf</string>
<string name="poi_waste_basket">Çöp tenekesi</string>
@ -766,7 +766,7 @@
<string name="poi_craft_agricultural_engines">Tarımsal motorlar</string>
<string name="poi_craft_blacksmith">Demirci</string>
<string name="poi_craft_brewery">Bira fabrikası</string>
<string name="poi_craft_boatbuilder">Boat builder</string>
<string name="poi_craft_boatbuilder">Gemi yapımcısı</string>
<string name="poi_craft_bookbinder">Ciltçi</string>
<string name="poi_craft_carpenter">Marangoz</string>
<string name="poi_craft_carpet_layer">Halı satıcısı</string>
@ -866,7 +866,7 @@
<string name="poi_daymark">Gün işareti</string>
<string name="poi_distance_mark">Mesafe işareti</string>
<string name="poi_dry_dock">Havuz</string>
<string name="poi_dyke">Lezbiyen</string>
<string name="poi_dyke">Su seti</string>
<string name="poi_landmark">Simgesel Yapı</string>
<string name="poi_seamark_light">Deniz işareti, ışık</string>
<string name="poi_seamark_light_major">Deniz işareti, büyük ışık</string>
@ -2148,7 +2148,7 @@
<string name="poi_clothes_costumes">Kostüm</string>
<string name="poi_clothes_traditional">Geleneksel</string>
<string name="poi_clothes_suits">Takım elbise</string>
<string name="poi_clothes_maternity">Hamile</string>
<string name="poi_clothes_maternity">Hamilelik</string>
<string name="poi_clothes_vintage">Nostalji</string>
<string name="poi_clothes_oversize">Büyük beden</string>
<string name="poi_clothes_schoolwear">Okul</string>
@ -2443,4 +2443,119 @@
<string name="poi_historic_railway_station">Tarihi tren istasyonu</string>
<string name="poi_historic_farm">Tarihi çiftlik</string>
<string name="poi_pa">Pa (müstahkem maori yerleşimi)</string>
<string name="poi_futsal">Futsal (salon futbolu)</string>
<string name="poi_atm_no">ATM: hayır</string>
<string name="poi_atm_yes">ATM: evet</string>
<string name="poi_boxing">Boks</string>
<string name="poi_lacrosse">Lakros</string>
<string name="poi_shuffleboard">Disk iteleme oyunu</string>
<string name="poi_squash">Squash (duvar tenisi)</string>
<string name="poi_rc_car">Uzaktan kumandalı araba yarışı</string>
<string name="poi_disc_golf">Disk golf</string>
<string name="poi_judo">Judo</string>
<string name="poi_badminton">Badminton</string>
<string name="poi_karting">Karting</string>
<string name="poi_netball">Netbol</string>
<string name="poi_running">Koşma</string>
<string name="poi_gaelic_games">Gal oyunları</string>
<string name="poi_dojo">Dojo</string>
<string name="poi_official_name">Resmi adı</string>
<string name="poi_parking_carports">Araç park çatısı</string>
<string name="poi_parking_garage_boxes">Garaj bölmeleri</string>
<string name="poi_parking_surface">Tür: yüzey</string>
<string name="poi_water_heater_no">Su ısıtıcısı: hayır</string>
<string name="poi_water_heater_yes">Su ısıtıcısı: evet</string>
<string name="poi_microwave_oven_no">Mikrodalga fırın: hayır</string>
<string name="poi_microwave_oven_yes">Mikrodalga fırın: evet</string>
<string name="poi_billiards">Bilardo</string>
<string name="poi_bathing_no">Yüzme: hayır</string>
<string name="poi_bathing_yes">Yüzme: evet</string>
<string name="poi_boat_storage">Tekne depolama</string>
<string name="poi_ref">Referans numarası</string>
<string name="poi_tunnel_ref">Tünel numarası</string>
<string name="poi_bridge_ref">Köprü numarası</string>
<string name="poi_conveying_yes">Taşıma: evet</string>
<string name="poi_aerialway_length">Uzunluk</string>
<string name="poi_fireworks">Havai fişek mağazası</string>
<string name="poi_craft_electronics_repair">Elektronik onarımı</string>
<string name="poi_hackerspace">Hackerspace</string>
<string name="poi_fitness_station">Fitness istasyonu</string>
<string name="poi_building_type_pyramid">Bina türü: piramit</string>
<string name="poi_health_specialty_palliative_medicine_yes">Palyatif tıp</string>
<string name="poi_health_specialty_behavior_yes">Davranış</string>
<string name="poi_health_specialty_depth_psychology_yes">Derinlik psikolojisi</string>
<string name="poi_health_specialty_naturopathy_yes">Naturopati</string>
<string name="poi_health_specialty_chiropractic_yes">Kayropraktik</string>
<string name="poi_health_specialty_herbalism_yes">Bitkisel tıp</string>
<string name="poi_health_specialty_reiki_yes">Reiki</string>
<string name="poi_health_specialty_traditional_chinese_medicine_yes">Geleneksel Çin tıbbı</string>
<string name="poi_health_specialty_homeopathy_yes">Homeopati</string>
<string name="poi_health_specialty_acupuncture_yes">Akupunktur</string>
<string name="poi_health_specialty_adult_psychiatry_yes">Yetişkin psikiyatrisi</string>
<string name="poi_health_specialty_podology_yes">Podoloji</string>
<string name="poi_health_specialty_sports_medicine_yes">Spor hekimliği</string>
<string name="poi_health_specialty_manual_therapy_yes">Manuel terapi</string>
<string name="poi_health_specialty_speech_therapy_yes">Konuşma terapisi</string>
<string name="poi_health_specialty_clinical_pathology_yes">Klinik patoloji</string>
<string name="poi_health_specialty_optometry_yes">Optometri</string>
<string name="poi_health_specialty_addiction_medicine_yes">Bağımlılık tedavisi</string>
<string name="poi_health_specialty_obstetrics_caesarean_section_no">Sağlık uzmanlığı: obstetrik (sezaryen): hayır</string>
<string name="poi_health_specialty_obstetrics_caesarean_section_yes">Obstetrik (sezaryen)</string>
<string name="poi_health_specialty_social_paediatrics_no">Sağlık uzmanlığı: sosyal pediatri: hayır</string>
<string name="poi_health_specialty_social_paediatrics_yes">Sosyal pediatri</string>
<string name="poi_health_specialty_obstetrics_antenatal_no">Sağlık uzmanlığı: obstetrik (doğum öncesi): hayır</string>
<string name="poi_health_specialty_obstetrics_antenatal_yes">Obstetrik (doğum öncesi)</string>
<string name="poi_health_specialty_obstetrics_postnatal_no">Sağlık uzmanlığı: obstetrik (doğum sonrası): hayır</string>
<string name="poi_health_specialty_obstetrics_postnatal_yes">Obstetrik (doğum sonrası)</string>
<string name="poi_health_specialty_tropical_medicine_no">Sağlık uzmanlığı: tropikal tıp: hayır</string>
<string name="poi_health_specialty_tropical_medicine_yes">Tropikal tıp</string>
<string name="poi_health_specialty_oncology_yes">Onkoloji</string>
<string name="poi_health_specialty_pathology_yes">Patolojik anatomi</string>
<string name="poi_health_specialty_nuclear_medicine_yes">Nükleer tıp</string>
<string name="poi_health_specialty_endocrinology_yes">Endokrinoloji</string>
<string name="poi_health_specialty_neuropsychiatry_yes">Nöropsikiyatri</string>
<string name="poi_health_specialty_neurosurgery_yes">Beyin ve sinir cerrahisi</string>
<string name="poi_health_specialty_nephrology_yes">Nefroloji (böbrek hastalıkları)</string>
<string name="poi_health_specialty_dentistry_yes">Diş hekimliği</string>
<string name="poi_health_specialty_gastroenterology_yes">Gastroenteroloji</string>
<string name="poi_health_specialty_diagnostic_radiology_yes">Tıbbi görüntüleme</string>
<string name="poi_health_specialty_maxillofacial_surgery_yes">Çene-yüz cerrahisi</string>
<string name="poi_health_specialty_physiatry_yes">Fiziksel tıp ve rehabilitasyon</string>
<string name="poi_health_specialty_child_psychiatry_yes">Çocuk psikiyatrisi</string>
<string name="poi_health_specialty_occupational_yes">İş terapisi</string>
<string name="poi_health_specialty_biochemistry_yes">Biyokimya</string>
<string name="poi_health_specialty_physiotherapy_yes">Fizyoterapi</string>
<string name="poi_health_specialty_orthodontics_yes">Ortodonti</string>
<string name="poi_health_specialty_plastic_surgery_yes">Plastik cerrahi</string>
<string name="poi_health_specialty_emergency_medicine_no">Sağlık uzmanlığı: kaza ve acil tıp: hayır</string>
<string name="poi_health_specialty_emergency_medicine_yes">Kaza ve acil tıp</string>
<string name="poi_health_specialty_maternity_yes">Hamilelik</string>
<string name="poi_health_specialty_dental_oral_maxillo_facial_surgery_yes">Diş, ağız ve çene-yüz cerrahisi</string>
<string name="poi_health_specialty_pulmonology_yes">Göğüs hastalıkları</string>
<string name="poi_health_specialty_anaesthetics_yes">Anesteziyoloji</string>
<string name="poi_health_specialty_osteopathy_yes">Osteopati</string>
<string name="poi_health_specialty_biology_yes">Klinik biyoloji</string>
<string name="poi_health_specialty_trauma_yes">Travmatoloji</string>
<string name="poi_health_specialty_cardiology_yes">Kardiyoloji</string>
<string name="poi_health_specialty_dermatovenereology_yes">Dermato-venereoloji</string>
<string name="poi_health_specialty_neurology_yes">Nöroloji</string>
<string name="poi_health_specialty_psychiatry_yes">Psikiyatri</string>
<string name="poi_health_specialty_radiotherapy_yes">Radyoterapi</string>
<string name="poi_health_specialty_radiology_yes">Radyoloji</string>
<string name="poi_health_specialty_surgery_yes">Genel cerrahi</string>
<string name="poi_health_specialty_urology_yes">Üroloji</string>
<string name="poi_health_specialty_dermatology_yes">Dermatoloji</string>
<string name="poi_health_specialty_paediatrics_no">Sağlık uzmanlığı: pediatri: hayır</string>
<string name="poi_health_specialty_paediatrics_yes">Pediatri</string>
<string name="poi_health_specialty_otolaryngology_yes">Kulak burun boğaz</string>
<string name="poi_health_specialty_orthopaedics_yes">Ortopedi</string>
<string name="poi_health_specialty_internal_medicine_yes">İç hastalıkları</string>
<string name="poi_health_specialty_gynaecology_yes">Jinekoloji</string>
<string name="poi_health_specialty_ophthalmology_yes">Oftalmoloji</string>
<string name="poi_health_specialty_general_yes">Pratisyen doktor</string>
<string name="poi_cargo_hgv">ır yük araçları</string>
<string name="poi_cargo_bicycle">Bisikletler</string>
<string name="poi_cargo_container">Konteynerler</string>
<string name="poi_cargo_vehicle">Araçlar</string>
<string name="poi_cargo_passengers">Yolcular</string>
</resources>

View file

@ -3859,4 +3859,5 @@
<string name="sort_last_modified">Son değiştirme</string>
<string name="sort_name_descending">İsim: Z A</string>
<string name="sort_name_ascending">İsim: A Z</string>
<string name="start_finish_icons">Başlangıç/bitiş simgeleri</string>
</resources>

View file

@ -3832,4 +3832,5 @@
<string name="poi_recycling_small_electrical_appliances">Невеликі електроприлади</string>
<string name="poi_beehive">Вулик</string>
<string name="poi_nuts">Насіннєвий магазин</string>
<string name="poi_fuel_lng">СПГ</string>
</resources>

View file

@ -3899,4 +3899,5 @@
<string name="sort_last_modified">Востаннє змінено</string>
<string name="sort_name_descending">За назвою: Я — А</string>
<string name="sort_name_ascending">За назвою: А — Я</string>
<string name="start_finish_icons">Піктограми початку/завершення</string>
</resources>

View file

@ -4257,5 +4257,7 @@
<string name="poi_nuts">Nut store</string>
<string name="poi_fuel_lng">LNG</string>
</resources>

View file

@ -11,6 +11,7 @@
Thx - Hardy
-->
<string name="contour_lines_thanks">Thank you for purchasing \'Contour lines\'</string>
<string name="start_finish_icons">Start/finish icons</string>
<string name="sort_name_ascending">Name: A Z</string>
<string name="sort_name_descending">Name: Z A</string>

View file

@ -0,0 +1,583 @@
package net.osmand.plus.inapp;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsResponseListener;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.util.Algorithms;
import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
// The helper object
private BillingManager billingManager;
private List<SkuDetails> skuDetailsList;
/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
* (that you got from the Google Play developer console). This is not your
* developer public key, it's the *app-specific* public key.
*
* Instead of just storing the entire literal string here embedded in the
* program, construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key. The key itself is not secret information, but we don't
* want to make it easy for an attacker to replace the public key with one
* of their own and then fake messages from the server.
*/
private static final String BASE64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" +
"UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" +
"wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" +
"OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" +
"I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" +
"YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB";
public InAppPurchaseHelperImpl(OsmandApplication ctx) {
super(ctx);
purchases = new InAppPurchasesImpl(ctx);
}
@Override
public void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback) {
if (callback != null) {
callback.onSuccess();
}
}
private BillingManager getBillingManager() {
return billingManager;
}
protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand runnable) {
billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() {
@Override
public void onBillingClientSetupFinished() {
logDebug("Setup finished.");
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
if (!billingManager.isServiceConnected()) {
// Oh noes, there was a problem.
//complain("Problem setting up in-app billing: " + result);
notifyError(taskType, billingManager.getBillingClientResponseMessage());
stop(true);
return;
}
runnable.run(InAppPurchaseHelperImpl.this);
}
@Override
public void onConsumeFinished(String token, BillingResult billingResult) {
}
@Override
public void onPurchasesUpdated(final List<Purchase> purchases) {
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) {
List<String> skuInApps = new ArrayList<>();
for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) {
skuInApps.add(purchase.getSku());
}
for (Purchase p : purchases) {
skuInApps.add(p.getSku());
}
billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListInApps) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
logError("Failed to query inapps sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<String> skuSubscriptions = new ArrayList<>();
for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) {
skuSubscriptions.add(subscription.getSku());
}
for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku());
}
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListSubscriptions) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<SkuDetails> skuDetailsList = new ArrayList<>(skuDetailsListInApps);
skuDetailsList.addAll(skuDetailsListSubscriptions);
InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList;
mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList);
}
});
}
});
}
for (Purchase purchase : purchases) {
if (!purchase.isAcknowledged()) {
onPurchaseFinished(purchase);
}
}
}
@Override
public void onPurchaseCanceled() {
stop(true);
}
});
}
@Override
public void purchaseFullVersion(@NonNull final Activity activity) {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
try {
SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku());
if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
commandDone();
} catch (Exception e) {
complain("Cannot launch full version purchase!");
logError("purchaseFullVersion Error", e);
stop(true);
}
}
});
}
@Override
public void purchaseDepthContours(@NonNull final Activity activity) {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
try {
SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku());
if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
commandDone();
} catch (Exception e) {
complain("Cannot launch depth contours purchase!");
logError("purchaseDepthContours Error", e);
stop(true);
}
}
});
}
@Override
public void purchaseContourLines(@NonNull Activity activity) throws UnsupportedOperationException {
OsmandPlugin plugin = OsmandPlugin.getPlugin(SRTMPlugin.class);
if(plugin == null || plugin.getInstallURL() == null) {
Toast.makeText(activity.getApplicationContext(),
activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show();
} else {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL())));
}
}
@Override
public void manageSubscription(@NonNull Context ctx, @Nullable String sku) {
String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName();
if (!Algorithms.isEmpty(sku)) {
url += "&sku=" + sku;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
ctx.startActivity(intent);
}
@Nullable
private SkuDetails getSkuDetails(@NonNull String sku) {
List<SkuDetails> skuDetailsList = this.skuDetailsList;
if (skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
if (details.getSku().equals(sku)) {
return details;
}
}
}
return null;
}
private boolean hasDetails(@NonNull String sku) {
return getSkuDetails(sku) != null;
}
@Nullable
private Purchase getPurchase(@NonNull String sku) {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
List<Purchase> purchases = billingManager.getPurchases();
if (purchases != null) {
for (Purchase p : purchases) {
if (p.getSku().equals(sku)) {
return p;
}
}
}
}
return null;
}
// Listener that's called when we finish querying the items and subscriptions we own
private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
@NonNull
private List<String> getAllOwnedSubscriptionSkus() {
List<String> result = new ArrayList<>();
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
for (Purchase p : billingManager.getPurchases()) {
if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) {
result.add(p.getSku());
}
}
}
return result;
}
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
logDebug("Query sku details finished.");
// Have we been disposed of in the meantime? If so, quit.
if (getBillingManager() == null) {
stop(true);
return;
}
// Is it a failure?
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
logError("Failed to query inventory: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
logDebug("Query sku details was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
List<String> allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus();
for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
if (hasDetails(s.getSku())) {
Purchase purchase = getPurchase(s.getSku());
SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku());
if (liveUpdatesDetails != null) {
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
}
allOwnedSubscriptionSkus.remove(s.getSku());
}
}
for (String sku : allOwnedSubscriptionSkus) {
Purchase purchase = getPurchase(sku);
SkuDetails liveUpdatesDetails = getSkuDetails(sku);
if (liveUpdatesDetails != null) {
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
if (s == null) {
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails);
}
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
}
}
InAppPurchase fullVersion = getFullVersion();
if (hasDetails(fullVersion.getSku())) {
Purchase purchase = getPurchase(fullVersion.getSku());
SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku());
if (fullPriceDetails != null) {
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase);
}
}
InAppPurchase depthContours = getDepthContours();
if (hasDetails(depthContours.getSku())) {
Purchase purchase = getPurchase(depthContours.getSku());
SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku());
if (depthContoursDetails != null) {
fetchInAppPurchase(depthContours, depthContoursDetails, purchase);
}
}
InAppPurchase contourLines = getContourLines();
if (hasDetails(contourLines.getSku())) {
Purchase purchase = getPurchase(contourLines.getSku());
SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku());
if (contourLinesDetails != null) {
fetchInAppPurchase(contourLines, contourLinesDetails, purchase);
}
}
Purchase fullVersionPurchase = getPurchase(fullVersion.getSku());
boolean fullVersionPurchased = fullVersionPurchase != null;
if (fullVersionPurchased) {
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
}
Purchase depthContoursPurchase = getPurchase(depthContours.getSku());
boolean depthContoursPurchased = depthContoursPurchase != null;
if (depthContoursPurchased) {
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
}
// Do we have the live updates?
boolean subscribedToLiveUpdates = false;
List<Purchase> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = getPurchase(p.getSku());
if (purchase != null) {
liveUpdatesPurchases.add(purchase);
if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true;
}
}
}
OsmandSettings.OsmandPreference<Long> subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME;
if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) {
if (subscriptionCancelledTime.get() == 0) {
subscriptionCancelledTime.set(System.currentTimeMillis());
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
} else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false);
}
}
} else if (subscribedToLiveUpdates) {
subscriptionCancelledTime.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
}
lastValidationCheckTime = System.currentTimeMillis();
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE")
+ " live updates purchased.");
OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true);
List<Purchase> tokensToSend = new ArrayList<>();
if (liveUpdatesPurchases.size() > 0) {
List<String> tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";"));
for (Purchase purchase : liveUpdatesPurchases) {
if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get()))
&& !Algorithms.isEmpty(purchase.getDeveloperPayload())) {
String payload = purchase.getDeveloperPayload();
if (!Algorithms.isEmpty(payload)) {
String[] arr = payload.split(" ");
if (arr.length > 0) {
settings.BILLING_USER_ID.set(arr[0]);
}
if (arr.length > 1) {
token = arr[1];
settings.BILLING_USER_TOKEN.set(token);
}
}
}
if (!tokensSent.contains(purchase.getSku())) {
tokensToSend.add(purchase);
}
}
}
List<PurchaseInfo> purchaseInfoList = new ArrayList<>();
for (Purchase purchase : tokensToSend) {
purchaseInfoList.add(getPurchaseInfo(purchase));
}
onSkuDetailsResponseDone(purchaseInfoList);
}
};
private PurchaseInfo getPurchaseInfo(Purchase purchase) {
return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken());
}
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
if (purchase != null) {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED);
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime());
} else {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED);
}
inAppPurchase.setPrice(skuDetails.getPrice());
inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode());
if (skuDetails.getPriceAmountMicros() > 0) {
inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d);
}
String subscriptionPeriod = skuDetails.getSubscriptionPeriod();
if (!Algorithms.isEmpty(subscriptionPeriod)) {
if (inAppPurchase instanceof InAppSubscription) {
try {
((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod);
} catch (ParseException e) {
LOG.error(e);
}
}
}
if (inAppPurchase instanceof InAppSubscription) {
String introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try {
s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));
} catch (ParseException e) {
LOG.error(e);
}
}
}
}
protected InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference<Activity> activity, final String sku, final String payload) {
return new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
try {
Activity a = activity.get();
SkuDetails skuDetails = getSkuDetails(sku);
if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.setPayload(payload);
billingManager.initiatePurchaseFlow(a, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
commandDone();
} else {
stop(true);
}
} catch (Exception e) {
logError("launchPurchaseFlow Error", e);
stop(true);
}
}
};
}
protected InAppCommand getRequestInventoryCommand() {
return new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
logDebug("Setup successful. Querying inventory.");
try {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.queryPurchases();
} else {
throw new IllegalStateException("BillingManager disposed");
}
commandDone();
} catch (Exception e) {
logError("queryInventoryAsync Error", e);
notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY);
stop(true);
}
}
};
}
// Call when a purchase is finished
private void onPurchaseFinished(Purchase purchase) {
logDebug("Purchase finished: " + purchase);
// if we were disposed of in the meantime, quit.
if (getBillingManager() == null) {
stop(true);
return;
}
onPurchaseDone(getPurchaseInfo(purchase));
}
@Override
protected boolean isBillingManagerExists() {
return getBillingManager() != null;
}
@Override
protected void destroyBillingManager() {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.destroy();
this.billingManager = null;
}
}
}

View file

@ -0,0 +1,323 @@
package net.osmand.plus.inapp;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.SkuDetails;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
public class InAppPurchasesImpl extends InAppPurchases {
private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion();
private static final InAppPurchaseDepthContoursFull DEPTH_CONTOURS_FULL = new InAppPurchaseDepthContoursFull();
private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree();
private static final InAppPurchaseContourLinesFull CONTOUR_LINES_FULL = new InAppPurchaseContourLinesFull();
private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree();
private static final InAppSubscription[] LIVE_UPDATES_FULL = new InAppSubscription[]{
new InAppPurchaseLiveUpdatesOldMonthlyFull(),
new InAppPurchaseLiveUpdatesMonthlyFull(),
new InAppPurchaseLiveUpdates3MonthsFull(),
new InAppPurchaseLiveUpdatesAnnualFull()
};
private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{
new InAppPurchaseLiveUpdatesOldMonthlyFree(),
new InAppPurchaseLiveUpdatesMonthlyFree(),
new InAppPurchaseLiveUpdates3MonthsFree(),
new InAppPurchaseLiveUpdatesAnnualFree()
};
public InAppPurchasesImpl(OsmandApplication ctx) {
super(ctx);
fullVersion = FULL_VERSION;
if (Version.isFreeVersion(ctx)) {
liveUpdates = new LiveUpdatesInAppPurchasesFree();
} else {
liveUpdates = new LiveUpdatesInAppPurchasesFull();
}
for (InAppSubscription s : liveUpdates.getAllSubscriptions()) {
if (s instanceof InAppPurchaseLiveUpdatesMonthly) {
if (s.isDiscounted()) {
discountedMonthlyLiveUpdates = s;
} else {
monthlyLiveUpdates = s;
}
}
}
if (Version.isFreeVersion(ctx)) {
depthContours = DEPTH_CONTOURS_FREE;
} else {
depthContours = DEPTH_CONTOURS_FULL;
}
if (Version.isFreeVersion(ctx)) {
contourLines = CONTOUR_LINES_FREE;
} else {
contourLines = CONTOUR_LINES_FULL;
}
inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines };
}
@Override
public boolean isFullVersion(String sku) {
return FULL_VERSION.getSku().equals(sku);
}
@Override
public boolean isDepthContours(String sku) {
return DEPTH_CONTOURS_FULL.getSku().equals(sku) || DEPTH_CONTOURS_FREE.getSku().equals(sku);
}
@Override
public boolean isContourLines(String sku) {
return CONTOUR_LINES_FULL.getSku().equals(sku) || CONTOUR_LINES_FREE.getSku().equals(sku);
}
@Override
public boolean isLiveUpdates(String sku) {
for (InAppPurchase p : LIVE_UPDATES_FULL) {
if (p.getSku().equals(sku)) {
return true;
}
}
for (InAppPurchase p : LIVE_UPDATES_FREE) {
if (p.getSku().equals(sku)) {
return true;
}
}
return false;
}
private static class InAppPurchaseFullVersion extends InAppPurchase {
private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price";
InAppPurchaseFullVersion() {
super(SKU_FULL_VERSION_PRICE);
}
@Override
public String getDefaultPrice(Context ctx) {
return ctx.getString(R.string.full_version_price);
}
}
private static class InAppPurchaseDepthContoursFull extends InAppPurchaseDepthContours {
private static final String SKU_DEPTH_CONTOURS_FULL = "net.osmand.seadepth_plus";
InAppPurchaseDepthContoursFull() {
super(SKU_DEPTH_CONTOURS_FULL);
}
}
private static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours {
private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth";
InAppPurchaseDepthContoursFree() {
super(SKU_DEPTH_CONTOURS_FREE);
}
}
private static class InAppPurchaseContourLinesFull extends InAppPurchaseContourLines {
private static final String SKU_CONTOUR_LINES_FULL = "net.osmand.contourlines_plus";
InAppPurchaseContourLinesFull() {
super(SKU_CONTOUR_LINES_FULL);
}
}
private static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines {
private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines";
InAppPurchaseContourLinesFree() {
super(SKU_CONTOUR_LINES_FREE);
}
}
private static class InAppPurchaseLiveUpdatesMonthlyFull extends InAppPurchaseLiveUpdatesMonthly {
private static final String SKU_LIVE_UPDATES_MONTHLY_FULL = "osm_live_subscription_monthly_full";
InAppPurchaseLiveUpdatesMonthlyFull() {
super(SKU_LIVE_UPDATES_MONTHLY_FULL, 1);
}
private InAppPurchaseLiveUpdatesMonthlyFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFull(sku) : null;
}
}
private static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly {
private static final String SKU_LIVE_UPDATES_MONTHLY_FREE = "osm_live_subscription_monthly_free";
InAppPurchaseLiveUpdatesMonthlyFree() {
super(SKU_LIVE_UPDATES_MONTHLY_FREE, 1);
}
private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null;
}
}
private static class InAppPurchaseLiveUpdates3MonthsFull extends InAppPurchaseLiveUpdates3Months {
private static final String SKU_LIVE_UPDATES_3_MONTHS_FULL = "osm_live_subscription_3_months_full";
InAppPurchaseLiveUpdates3MonthsFull() {
super(SKU_LIVE_UPDATES_3_MONTHS_FULL, 1);
}
private InAppPurchaseLiveUpdates3MonthsFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFull(sku) : null;
}
}
private static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months {
private static final String SKU_LIVE_UPDATES_3_MONTHS_FREE = "osm_live_subscription_3_months_free";
InAppPurchaseLiveUpdates3MonthsFree() {
super(SKU_LIVE_UPDATES_3_MONTHS_FREE, 1);
}
private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null;
}
}
private static class InAppPurchaseLiveUpdatesAnnualFull extends InAppPurchaseLiveUpdatesAnnual {
private static final String SKU_LIVE_UPDATES_ANNUAL_FULL = "osm_live_subscription_annual_full";
InAppPurchaseLiveUpdatesAnnualFull() {
super(SKU_LIVE_UPDATES_ANNUAL_FULL, 1);
}
private InAppPurchaseLiveUpdatesAnnualFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFull(sku) : null;
}
}
private static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual {
private static final String SKU_LIVE_UPDATES_ANNUAL_FREE = "osm_live_subscription_annual_free";
InAppPurchaseLiveUpdatesAnnualFree() {
super(SKU_LIVE_UPDATES_ANNUAL_FREE, 1);
}
private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null;
}
}
private static class InAppPurchaseLiveUpdatesOldMonthlyFull extends InAppPurchaseLiveUpdatesOldMonthly {
private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FULL = "osm_live_subscription_2";
InAppPurchaseLiveUpdatesOldMonthlyFull() {
super(SKU_LIVE_UPDATES_OLD_MONTHLY_FULL);
}
}
private static class InAppPurchaseLiveUpdatesOldMonthlyFree extends InAppPurchaseLiveUpdatesOldMonthly {
private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FREE = "osm_free_live_subscription_2";
InAppPurchaseLiveUpdatesOldMonthlyFree() {
super(SKU_LIVE_UPDATES_OLD_MONTHLY_FREE);
}
}
public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription {
private SkuDetails details;
InAppPurchaseLiveUpdatesOldSubscription(@NonNull SkuDetails details) {
super(details.getSku(), true);
this.details = details;
}
@Override
public String getDefaultPrice(Context ctx) {
return "";
}
@Override
public CharSequence getTitle(Context ctx) {
return details.getTitle();
}
@Override
public CharSequence getDescription(@NonNull Context ctx) {
return details.getDescription();
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return null;
}
}
private static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList {
public LiveUpdatesInAppPurchasesFree() {
super(LIVE_UPDATES_FREE);
}
}
private static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList {
public LiveUpdatesInAppPurchasesFull() {
super(LIVE_UPDATES_FULL);
}
}
}

View file

@ -0,0 +1,96 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Signature related tools.
*
* @since 2019/12/9
*/
public class CipherUtil {
private static final String TAG = "CipherUtil";
private static final String SIGN_ALGORITHMS = "SHA256WithRSA";
private static final String PUBLIC_KEY = "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsB+oH8rYQncwpTqGa0kS/5E725HJrq2sW1ThAZtmorYVi52Yt9PmZvNDz7284ol9C2skrKQR34eIer8Tr7Qqq3mlNo+/LVUpq9sa++kB2glaG6jj5NNjM3w4nVYHFIYkd5AQhodJgmqFvnp2s7r7YmyQVXZSehei5bA1G70Bs+El9cSv9shNNGTCaU3ARUu2hy3Ltkc/ov7/ZYYpiwjbyD3cmoMh9jO1++zztXb2phjv1h9zeJOp1i6HsotZll+c9J4jjV3GhrF+ZJm5WrSzGLDLtwSldRpMZFxrSvAJJstjzhDz3LpUM+nPV3HZ5VQ/xosmwWYmiibo89E1gw8p73NTBXHzuQMJcTJ6vTjD8LeMskpXHZUAGhifmFLGN1LbNP9662ulCV12kIbXuzWCwwi/h0DWqmnjKmLvzc88e4BrGrp2zZUnHz7m15voPG+4cQ3z9+cwS4gEI3SUTiFyQGE539SO/11VkkQAJ8P7du1JFNqQw5ZEW3AoE1iUsp5XAgMBAAE=";
/**
* the method to check the signature for the data returned from the interface
* @param content Unsigned data
* @param sign the signature for content
* @param publicKey the public of the application
* @return boolean
*/
public static boolean doCheck(String content, String sign, String publicKey) {
if (TextUtils.isEmpty(publicKey)) {
Log.e(TAG, "publicKey is null");
return false;
}
if (TextUtils.isEmpty(content) || TextUtils.isEmpty(sign)) {
Log.e(TAG, "data is error");
return false;
}
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.decode(publicKey, Base64.DEFAULT);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update(content.getBytes("UTF-8"));
boolean bverify = signature.verify(Base64.decode(sign, Base64.DEFAULT));
return bverify;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "doCheck NoSuchAlgorithmException" + e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "doCheck InvalidKeySpecException" + e);
} catch (InvalidKeyException e) {
Log.e(TAG, "doCheck InvalidKeyException" + e);
} catch (SignatureException e) {
Log.e(TAG, "doCheck SignatureException" + e);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "doCheck UnsupportedEncodingException" + e);
}
return false;
}
/**
* get the publicKey of the application
* During the encoding process, avoid storing the public key in clear text.
* @return publickey
*/
public static String getPublicKey(){
return PUBLIC_KEY;
}
}

View file

@ -0,0 +1,33 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
/**
* Constants Class.
*
* @since 2019/12/9
*/
public class Constants {
/** requestCode for pull up the pmsPay page */
public static final int REQ_CODE_BUY_SUB = 4002;
public static final int REQ_CODE_BUY_INAPP = 4003;
/** requestCode for pull up the login page for isEnvReady interface */
public static final int REQ_CODE_LOGIN = 2001;
}

View file

@ -0,0 +1,103 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
import android.app.Activity;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.huawei.hms.iap.IapApiException;
import com.huawei.hms.iap.entity.OrderStatusCode;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
/**
* Handles the exception returned from the iap api.
*
* @since 2019/12/9
*/
public class ExceptionHandle {
protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(ExceptionHandle.class);
/**
* The exception is solved.
*/
public static final int SOLVED = 0;
/**
* Handles the exception returned from the iap api.
* @param activity The Activity to call the iap api.
* @param e The exception returned from the iap api.
* @return int
*/
public static int handle(@Nullable Activity activity, Exception e) {
if (e instanceof IapApiException) {
IapApiException iapApiException = (IapApiException) e;
LOG.info("returnCode: " + iapApiException.getStatusCode());
switch (iapApiException.getStatusCode()) {
case OrderStatusCode.ORDER_STATE_CANCEL:
showToast(activity, "Order has been canceled!");
return SOLVED;
case OrderStatusCode.ORDER_STATE_PARAM_ERROR:
showToast(activity, "Order state param error!");
return SOLVED;
case OrderStatusCode.ORDER_STATE_NET_ERROR:
showToast(activity, "Order state net error!");
return SOLVED;
case OrderStatusCode.ORDER_VR_UNINSTALL_ERROR:
showToast(activity, "Order vr uninstall error!");
return SOLVED;
case OrderStatusCode.ORDER_HWID_NOT_LOGIN:
IapRequestHelper.startResolutionForResult(activity, iapApiException.getStatus(), Constants.REQ_CODE_LOGIN);
return SOLVED;
case OrderStatusCode.ORDER_PRODUCT_OWNED:
showToast(activity, "Product already owned error!");
return OrderStatusCode.ORDER_PRODUCT_OWNED;
case OrderStatusCode.ORDER_PRODUCT_NOT_OWNED:
showToast(activity, "Product not owned error!");
return SOLVED;
case OrderStatusCode.ORDER_PRODUCT_CONSUMED:
showToast(activity, "Product consumed error!");
return SOLVED;
case OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED:
showToast(activity, "Order account area not supported error!");
return SOLVED;
case OrderStatusCode.ORDER_NOT_ACCEPT_AGREEMENT:
showToast(activity, "User does not agree the agreement");
return SOLVED;
default:
// handle other error scenarios
showToast(activity, "Order unknown error!");
return SOLVED;
}
} else {
showToast(activity, "External error");
LOG.error(e.getMessage(), e);
return SOLVED;
}
}
private static void showToast(@Nullable Activity activity, String s) {
if (AndroidUtils.isActivityNotDestroyed(activity)) {
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
}
}

View file

@ -0,0 +1,37 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
/**
* Used to callback the result from iap api.
*
* @since 2019/12/9
*/
public interface IapApiCallback<T> {
/**
* The request is successful.
* @param result The result of a successful response.
*/
void onSuccess(T result);
/**
* Callback fail.
* @param e An Exception from IAPSDK.
*/
void onFail(Exception e);
}

View file

@ -0,0 +1,351 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
import android.app.Activity;
import android.content.IntentSender;
import android.text.TextUtils;
import android.util.Log;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.iap.Iap;
import com.huawei.hms.iap.IapApiException;
import com.huawei.hms.iap.IapClient;
import com.huawei.hms.iap.entity.ConsumeOwnedPurchaseReq;
import com.huawei.hms.iap.entity.ConsumeOwnedPurchaseResult;
import com.huawei.hms.iap.entity.IsEnvReadyResult;
import com.huawei.hms.iap.entity.OwnedPurchasesReq;
import com.huawei.hms.iap.entity.OwnedPurchasesResult;
import com.huawei.hms.iap.entity.ProductInfoReq;
import com.huawei.hms.iap.entity.ProductInfoResult;
import com.huawei.hms.iap.entity.PurchaseIntentReq;
import com.huawei.hms.iap.entity.PurchaseIntentResult;
import com.huawei.hms.iap.entity.StartIapActivityReq;
import com.huawei.hms.iap.entity.StartIapActivityResult;
import com.huawei.hms.support.api.client.Status;
import java.util.List;
/**
* The tool class of Iap interface.
*
* @since 2019/12/9
*/
public class IapRequestHelper {
private final static String TAG = "IapRequestHelper";
/**
* Create a PurchaseIntentReq object.
* @param type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param productId ID of the in-app product to be paid.
* The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect.
* @return PurchaseIntentReq
*/
private static PurchaseIntentReq createPurchaseIntentReq(int type, String productId) {
PurchaseIntentReq req = new PurchaseIntentReq();
req.setPriceType(type);
req.setProductId(productId);
req.setDeveloperPayload("testPurchase");
return req;
}
/**
* Create a ConsumeOwnedPurchaseReq object.
* @param purchaseToken which is generated by the Huawei payment server during product payment and returned to the app through InAppPurchaseData.
* The app transfers this parameter for the Huawei payment server to update the order status and then deliver the in-app product.
* @return ConsumeOwnedPurchaseReq
*/
private static ConsumeOwnedPurchaseReq createConsumeOwnedPurchaseReq(String purchaseToken) {
ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq();
req.setPurchaseToken(purchaseToken);
req.setDeveloperChallenge("testConsume");
return req;
}
/**
* Create a OwnedPurchasesReq object.
* @param type type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param continuationToken A data location flag which returns from obtainOwnedPurchases api or obtainOwnedPurchaseRecord api.
* @return OwnedPurchasesReq
*/
private static OwnedPurchasesReq createOwnedPurchasesReq(int type, String continuationToken) {
OwnedPurchasesReq req = new OwnedPurchasesReq();
req.setPriceType(type);
req.setContinuationToken(continuationToken);
return req;
}
/**
* Create a ProductInfoReq object.
* @param type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param productIds ID list of products to be queried. Each product ID must exist and be unique in the current app.
* @return ProductInfoReq
*/
private static ProductInfoReq createProductInfoReq(int type, List<String> productIds) {
ProductInfoReq req = new ProductInfoReq();
req.setPriceType(type);
req.setProductIds(productIds);
return req;
}
/**
* To check whether the country or region of the logged in HUAWEI ID is included in the countries or regions supported by HUAWEI IAP.
* @param mClient IapClient instance to call the isEnvReady API.
* @param callback IapApiCallback.
*/
public static void isEnvReady(IapClient mClient, final IapApiCallback callback) {
Log.i(TAG, "call isEnvReady");
Task<IsEnvReadyResult> task = mClient.isEnvReady();
task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {
@Override
public void onSuccess(IsEnvReadyResult result) {
Log.i(TAG, "isEnvReady, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "isEnvReady, fail");
callback.onFail(e);
}
});
}
/**
* Obtain in-app product details configured in AppGallery Connect.
* @param iapClient IapClient instance to call the obtainProductInfo API.
* @param productIds ID list of products to be queried. Each product ID must exist and be unique in the current app.
* @param type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param callback IapApiCallback
*/
public static void obtainProductInfo(IapClient iapClient, final List<String> productIds, int type, final IapApiCallback callback) {
Log.i(TAG, "call obtainProductInfo");
Task<ProductInfoResult> task = iapClient.obtainProductInfo(createProductInfoReq(type, productIds));
task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {
@Override
public void onSuccess(ProductInfoResult result) {
Log.i(TAG, "obtainProductInfo, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "obtainProductInfo, fail");
callback.onFail(e);
}
});
}
/**
* create orders for in-app products in the PMS
* @param iapClient IapClient instance to call the createPurchaseIntent API.
* @param productId ID of the in-app product to be paid.
* The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect.
* @param type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param callback IapApiCallback
*/
public static void createPurchaseIntent(final IapClient iapClient, String productId, int type, final IapApiCallback callback) {
Log.i(TAG, "call createPurchaseIntent");
Task<PurchaseIntentResult> task = iapClient.createPurchaseIntent(createPurchaseIntentReq(type, productId));
task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {
@Override
public void onSuccess(PurchaseIntentResult result) {
Log.i(TAG, "createPurchaseIntent, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "createPurchaseIntent, fail");
callback.onFail(e);
}
});
}
public static void createPurchaseIntent(final IapClient iapClient, String productId, int type, String payload, final IapApiCallback callback) {
Log.i(TAG, "call createPurchaseIntent");
PurchaseIntentReq req = createPurchaseIntentReq(type, productId);
req.setDeveloperPayload(payload);
Task<PurchaseIntentResult> task = iapClient.createPurchaseIntent(req);
task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {
@Override
public void onSuccess(PurchaseIntentResult result) {
Log.i(TAG, "createPurchaseIntent, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "createPurchaseIntent, fail");
callback.onFail(e);
}
});
}
/**
* to start an activity.
* @param activity the activity to launch a new page.
* @param status This parameter contains the pendingIntent object of the payment page.
* @param reqCode Result code.
*/
public static void startResolutionForResult(Activity activity, Status status, int reqCode) {
if (status == null) {
Log.e(TAG, "status is null");
return;
}
if (status.hasResolution()) {
try {
status.startResolutionForResult(activity, reqCode);
} catch (IntentSender.SendIntentException exp) {
Log.e(TAG, exp.getMessage());
}
} else {
Log.e(TAG, "intent is null");
}
}
/**
* query information about all subscribed in-app products, including consumables, non-consumables, and auto-renewable subscriptions.</br>
* If consumables are returned, the system needs to deliver them and calls the consumeOwnedPurchase API to consume the products.
* If non-consumables are returned, the in-app products do not need to be consumed.
* If subscriptions are returned, all existing subscription relationships of the user under the app are returned.
* @param mClient IapClient instance to call the obtainOwnedPurchases API.
* @param type In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription
* @param callback IapApiCallback
*/
public static void obtainOwnedPurchases(IapClient mClient, final int type, String continuationToken, final IapApiCallback callback) {
Log.i(TAG, "call obtainOwnedPurchases");
Task<OwnedPurchasesResult> task = mClient.obtainOwnedPurchases(IapRequestHelper.createOwnedPurchasesReq(type, continuationToken));
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
@Override
public void onSuccess(OwnedPurchasesResult result) {
Log.i(TAG, "obtainOwnedPurchases, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "obtainOwnedPurchases, fail");
callback.onFail(e);
}
});
}
/**
* obtain the historical consumption information about a consumable in-app product or all subscription receipts of a subscription.
* @param iapClient IapClient instance to call the obtainOwnedPurchaseRecord API.
* @param priceType In-app product type.
* The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription.
* @param continuationToken Data locating flag for supporting query in pagination mode.
* @param callback IapApiCallback
*/
public static void obtainOwnedPurchaseRecord(IapClient iapClient, int priceType, String continuationToken, final IapApiCallback callback) {
Log.i(TAG, "call obtainOwnedPurchaseRecord");
Task<OwnedPurchasesResult> task = iapClient.obtainOwnedPurchaseRecord(createOwnedPurchasesReq(priceType, continuationToken));
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
@Override
public void onSuccess(OwnedPurchasesResult result) {
Log.i(TAG, "obtainOwnedPurchaseRecord, success");
callback.onSuccess(result);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "obtainOwnedPurchaseRecord, fail");
callback.onFail(e);
}
});
}
/**
* Consume all the unconsumed purchases with priceType 0.
* @param iapClient IapClient instance to call the consumeOwnedPurchase API.
* @param purchaseToken which is generated by the Huawei payment server during product payment and returned to the app through InAppPurchaseData.
*/
public static void consumeOwnedPurchase(IapClient iapClient, String purchaseToken) {
Log.i(TAG, "call consumeOwnedPurchase");
Task<ConsumeOwnedPurchaseResult> task = iapClient.consumeOwnedPurchase(createConsumeOwnedPurchaseReq(purchaseToken));
task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() {
@Override
public void onSuccess(ConsumeOwnedPurchaseResult result) {
// Consume success.
Log.i(TAG, "consumeOwnedPurchase success");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException)e;
int returnCode = apiException.getStatusCode();
Log.e(TAG, "consumeOwnedPurchase fail, IapApiException returnCode: " + returnCode);
} else {
// Other external errors
Log.e(TAG, e.getMessage());
}
}
});
}
/**
* link to subscription manager page
* @param activity activity
* @param productId the productId of the subscription product
*/
public static void showSubscription(final Activity activity, String productId) {
StartIapActivityReq req = new StartIapActivityReq();
if (TextUtils.isEmpty(productId)) {
req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_MANAGER_ACTIVITY);
} else {
req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_EDIT_ACTIVITY);
req.setSubscribeProductId(productId);
}
IapClient iapClient = Iap.getIapClient(activity);
Task<StartIapActivityResult> task = iapClient.startIapActivity(req);
task.addOnSuccessListener(new OnSuccessListener<StartIapActivityResult>() {
@Override
public void onSuccess(StartIapActivityResult result) {
if(result != null) {
result.startActivity(activity);
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
ExceptionHandle.handle(activity, e);
}
});
}
}

View file

@ -0,0 +1,703 @@
package net.osmand.plus.inapp;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.iap.Iap;
import com.huawei.hms.iap.IapClient;
import com.huawei.hms.iap.entity.InAppPurchaseData;
import com.huawei.hms.iap.entity.IsEnvReadyResult;
import com.huawei.hms.iap.entity.OrderStatusCode;
import com.huawei.hms.iap.entity.OwnedPurchasesResult;
import com.huawei.hms.iap.entity.ProductInfo;
import com.huawei.hms.iap.entity.ProductInfoResult;
import com.huawei.hms.iap.entity.PurchaseIntentResult;
import com.huawei.hms.iap.entity.PurchaseResultInfo;
import com.huawei.hms.iap.entity.StartIapActivityReq;
import com.huawei.hms.iap.entity.StartIapActivityResult;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference;
import net.osmand.util.Algorithms;
import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
private boolean envReady = false;
private boolean purchaseSupported = false;
private List<ProductInfo> productInfos;
private OwnedPurchasesResult ownedSubscriptions;
private List<OwnedPurchasesResult> ownedInApps = new ArrayList<>();
public InAppPurchaseHelperImpl(OsmandApplication ctx) {
super(ctx);
purchases = new InAppPurchasesImpl(ctx);
}
@Override
public void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback) {
if (envReady) {
if (callback != null) {
if (purchaseSupported) {
callback.onSuccess();
} else {
callback.onFail();
}
}
} else {
// Initiating an isEnvReady request when entering the app.
// Check if the account service country supports IAP.
IapClient mClient = Iap.getIapClient(activity);
final WeakReference<Activity> activityRef = new WeakReference<>(activity);
IapRequestHelper.isEnvReady(mClient, new IapApiCallback<IsEnvReadyResult>() {
private void onReady(boolean succeed) {
logDebug("Setup finished.");
envReady = true;
purchaseSupported = succeed;
if (callback != null) {
if (succeed) {
callback.onSuccess();
} else {
callback.onFail();
}
}
}
@Override
public void onSuccess(IsEnvReadyResult result) {
onReady(true);
}
@Override
public void onFail(Exception e) {
onReady(false);
LOG.error("isEnvReady fail, " + e.getMessage(), e);
ExceptionHandle.handle(activityRef.get(), e);
}
});
}
}
protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand command) {
if (envReady) {
command.run(this);
} else {
command.commandDone();
}
}
private InAppCommand getPurchaseInAppCommand(@NonNull final Activity activity, @NonNull final String productId) throws UnsupportedOperationException {
return new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
try {
ProductInfo productInfo = getProductInfo(productId);
if (productInfo != null) {
IapRequestHelper.createPurchaseIntent(getIapClient(), productInfo.getProductId(),
IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback<PurchaseIntentResult>() {
@Override
public void onSuccess(PurchaseIntentResult result) {
if (result == null) {
logError("result is null");
} else {
// you should pull up the page to complete the payment process
IapRequestHelper.startResolutionForResult(activity, result.getStatus(), Constants.REQ_CODE_BUY_INAPP);
}
commandDone();
}
@Override
public void onFail(Exception e) {
int errorCode = ExceptionHandle.handle(activity, e);
if (errorCode != ExceptionHandle.SOLVED) {
logDebug("createPurchaseIntent, returnCode: " + errorCode);
if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) {
logError("already own this product");
} else {
logError("unknown error");
}
}
commandDone();
}
});
} else {
commandDone();
}
} catch (Exception e) {
complain("Cannot launch full version purchase!");
logError("purchaseFullVersion Error", e);
stop(true);
}
}
};
}
@Override
public void purchaseFullVersion(@NonNull final Activity activity) throws UnsupportedOperationException {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, getPurchaseInAppCommand(activity, purchases.getFullVersion().getSku()));
}
@Override
public void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, getPurchaseInAppCommand(activity, purchases.getDepthContours().getSku()));
}
@Override
public void purchaseContourLines(@NonNull Activity activity) throws UnsupportedOperationException {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES);
exec(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES, getPurchaseInAppCommand(activity, purchases.getContourLines().getSku()));
}
@Override
public void manageSubscription(@NonNull Context ctx, @Nullable String sku) {
if (uiActivity != null) {
StartIapActivityReq req = new StartIapActivityReq();
if (!Algorithms.isEmpty(sku)) {
req.setSubscribeProductId(sku);
req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_EDIT_ACTIVITY);
} else {
req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_MANAGER_ACTIVITY);
}
Task<StartIapActivityResult> task = getIapClient().startIapActivity(req);
task.addOnSuccessListener(new OnSuccessListener<StartIapActivityResult>() {
@Override
public void onSuccess(StartIapActivityResult result) {
logDebug("startIapActivity: onSuccess");
Activity activity = (Activity) uiActivity;
if (result != null && AndroidUtils.isActivityNotDestroyed(activity)) {
result.startActivity(activity);
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
logDebug("startIapActivity: onFailure");
}
});
}
}
@Nullable
private ProductInfo getProductInfo(@NonNull String productId) {
List<ProductInfo> productInfos = this.productInfos;
if (productInfos != null) {
for (ProductInfo info : productInfos) {
if (info.getProductId().equals(productId)) {
return info;
}
}
}
return null;
}
private boolean hasDetails(@NonNull String productId) {
return getProductInfo(productId) != null;
}
@Nullable
private InAppPurchaseData getPurchaseData(@NonNull String productId) {
InAppPurchaseData data = SubscriptionUtils.getPurchaseData(ownedSubscriptions, productId);
if (data == null) {
for (OwnedPurchasesResult result : ownedInApps) {
data = InAppUtils.getPurchaseData(result, productId);
if (data != null) {
break;
}
}
}
return data;
}
private PurchaseInfo getPurchaseInfo(InAppPurchaseData purchase) {
return new PurchaseInfo(purchase.getProductId(), purchase.getOrderID(), purchase.getPurchaseToken());
}
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull ProductInfo productInfo, @Nullable InAppPurchaseData purchaseData) {
if (purchaseData != null) {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED);
inAppPurchase.setPurchaseTime(purchaseData.getPurchaseTime());
} else {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED);
}
inAppPurchase.setPrice(productInfo.getPrice());
inAppPurchase.setPriceCurrencyCode(productInfo.getCurrency());
if (productInfo.getMicrosPrice() > 0) {
inAppPurchase.setPriceValue(productInfo.getMicrosPrice() / 1000000d);
}
String subscriptionPeriod = productInfo.getSubPeriod();
if (!Algorithms.isEmpty(subscriptionPeriod)) {
if (inAppPurchase instanceof InAppSubscription) {
try {
((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod);
} catch (ParseException e) {
LOG.error(e);
}
}
}
if (inAppPurchase instanceof InAppSubscription) {
String introductoryPrice = productInfo.getSubSpecialPrice();
String introductoryPricePeriod = productInfo.getSubPeriod();
int introductoryPriceCycles = productInfo.getSubSpecialPeriodCycles();
long introductoryPriceAmountMicros = productInfo.getSubSpecialPriceMicros();
if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try {
s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, String.valueOf(introductoryPriceCycles)));
} catch (ParseException e) {
LOG.error(e);
}
}
}
}
protected InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference<Activity> activity, final String sku, final String payload) {
return new InAppCommand() {
@Override
public void run(InAppPurchaseHelper helper) {
try {
Activity a = activity.get();
ProductInfo productInfo = getProductInfo(sku);
if (AndroidUtils.isActivityNotDestroyed(a) && productInfo != null) {
IapRequestHelper.createPurchaseIntent(getIapClient(), sku,
IapClient.PriceType.IN_APP_SUBSCRIPTION, payload, new IapApiCallback<PurchaseIntentResult>() {
@Override
public void onSuccess(PurchaseIntentResult result) {
if (result == null) {
logError("GetBuyIntentResult is null");
} else {
Activity a = activity.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
IapRequestHelper.startResolutionForResult(a, result.getStatus(), Constants.REQ_CODE_BUY_SUB);
} else {
logError("startResolutionForResult on destroyed activity");
}
}
commandDone();
}
@Override
public void onFail(Exception e) {
int errorCode = ExceptionHandle.handle(activity.get(), e);
if (ExceptionHandle.SOLVED != errorCode) {
logError("createPurchaseIntent, returnCode: " + errorCode);
if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) {
logError("already own this product");
} else {
logError("unknown error");
}
}
commandDone();
}
});
} else {
stop(true);
}
} catch (Exception e) {
logError("launchPurchaseFlow Error", e);
stop(true);
}
}
};
}
@Override
protected InAppCommand getRequestInventoryCommand() {
return new InAppCommand() {
@Override
protected void commandDone() {
super.commandDone();
inventoryRequested = false;
}
@Override
public void run(InAppPurchaseHelper helper) {
logDebug("Setup successful. Querying inventory.");
try {
productInfos = new ArrayList<>();
obtainOwnedSubscriptions();
} catch (Exception e) {
logError("queryInventoryAsync Error", e);
notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY);
stop(true);
commandDone();
}
}
private void obtainOwnedSubscriptions() {
if (uiActivity != null) {
IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_SUBSCRIPTION,
null, new IapApiCallback<OwnedPurchasesResult>() {
@Override
public void onSuccess(OwnedPurchasesResult result) {
ownedSubscriptions = result;
obtainOwnedInApps(null);
}
@Override
public void onFail(Exception e) {
logError("obtainOwnedSubscriptions exception", e);
ExceptionHandle.handle((Activity) uiActivity, e);
commandDone();
}
});
} else {
commandDone();
}
}
private void obtainOwnedInApps(final String continuationToken) {
if (uiActivity != null) {
// Query users' purchased non-consumable products.
IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_NONCONSUMABLE,
continuationToken, new IapApiCallback<OwnedPurchasesResult>() {
@Override
public void onSuccess(OwnedPurchasesResult result) {
ownedInApps.add(result);
if (result != null && !TextUtils.isEmpty(result.getContinuationToken())) {
obtainOwnedInApps(result.getContinuationToken());
} else {
obtainSubscriptionsInfo();
}
}
@Override
public void onFail(Exception e) {
logError("obtainOwnedInApps exception", e);
ExceptionHandle.handle((Activity) uiActivity, e);
commandDone();
}
});
} else {
commandDone();
}
}
private void obtainSubscriptionsInfo() {
if (uiActivity != null) {
Set<String> productIds = new HashSet<>();
List<InAppSubscription> subscriptions = purchases.getLiveUpdates().getAllSubscriptions();
for (InAppSubscription s : subscriptions) {
productIds.add(s.getSku());
}
productIds.addAll(ownedSubscriptions.getItemList());
IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds),
IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback<ProductInfoResult>() {
@Override
public void onSuccess(final ProductInfoResult result) {
if (result != null && result.getProductInfoList() != null) {
productInfos.addAll(result.getProductInfoList());
}
obtainInAppsInfo();
}
@Override
public void onFail(Exception e) {
int errorCode = ExceptionHandle.handle((Activity) uiActivity, e);
if (ExceptionHandle.SOLVED != errorCode) {
LOG.error("Unknown error");
}
commandDone();
}
});
} else {
commandDone();
}
}
private void obtainInAppsInfo() {
if (uiActivity != null) {
Set<String> productIds = new HashSet<>();
for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) {
productIds.add(purchase.getSku());
}
for (OwnedPurchasesResult result : ownedInApps) {
productIds.addAll(result.getItemList());
}
IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds),
IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback<ProductInfoResult>() {
@Override
public void onSuccess(ProductInfoResult result) {
if (result != null && result.getProductInfoList() != null) {
productInfos.addAll(result.getProductInfoList());
}
processInventory();
}
@Override
public void onFail(Exception e) {
int errorCode = ExceptionHandle.handle((Activity) uiActivity, e);
if (ExceptionHandle.SOLVED != errorCode) {
LOG.error("Unknown error");
}
commandDone();
}
});
} else {
commandDone();
}
}
private void processInventory() {
logDebug("Query sku details was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct!
*/
List<String> allOwnedSubscriptionSkus = ownedSubscriptions.getItemList();
for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
if (hasDetails(s.getSku())) {
InAppPurchaseData purchaseData = getPurchaseData(s.getSku());
ProductInfo liveUpdatesInfo = getProductInfo(s.getSku());
if (liveUpdatesInfo != null) {
fetchInAppPurchase(s, liveUpdatesInfo, purchaseData);
}
allOwnedSubscriptionSkus.remove(s.getSku());
}
}
for (String sku : allOwnedSubscriptionSkus) {
InAppPurchaseData purchaseData = getPurchaseData(sku);
ProductInfo liveUpdatesInfo = getProductInfo(sku);
if (liveUpdatesInfo != null) {
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
if (s == null) {
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesInfo);
}
fetchInAppPurchase(s, liveUpdatesInfo, purchaseData);
}
}
InAppPurchase fullVersion = getFullVersion();
if (hasDetails(fullVersion.getSku())) {
InAppPurchaseData purchaseData = getPurchaseData(fullVersion.getSku());
ProductInfo fullPriceDetails = getProductInfo(fullVersion.getSku());
if (fullPriceDetails != null) {
fetchInAppPurchase(fullVersion, fullPriceDetails, purchaseData);
}
}
InAppPurchase depthContours = getDepthContours();
if (hasDetails(depthContours.getSku())) {
InAppPurchaseData purchaseData = getPurchaseData(depthContours.getSku());
ProductInfo depthContoursDetails = getProductInfo(depthContours.getSku());
if (depthContoursDetails != null) {
fetchInAppPurchase(depthContours, depthContoursDetails, purchaseData);
}
}
InAppPurchase contourLines = getContourLines();
if (hasDetails(contourLines.getSku())) {
InAppPurchaseData purchaseData = getPurchaseData(contourLines.getSku());
ProductInfo contourLinesDetails = getProductInfo(contourLines.getSku());
if (contourLinesDetails != null) {
fetchInAppPurchase(contourLines, contourLinesDetails, purchaseData);
}
}
if (getPurchaseData(fullVersion.getSku()) != null) {
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
}
if (getPurchaseData(depthContours.getSku()) != null) {
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
}
if (getPurchaseData(contourLines.getSku()) != null) {
ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true);
}
// Do we have the live updates?
boolean subscribedToLiveUpdates = false;
List<InAppPurchaseData> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
InAppPurchaseData purchaseData = getPurchaseData(p.getSku());
if (purchaseData != null) {
liveUpdatesPurchases.add(purchaseData);
if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true;
}
}
}
OsmandPreference<Long> subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME;
if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) {
if (subscriptionCancelledTime.get() == 0) {
subscriptionCancelledTime.set(System.currentTimeMillis());
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
} else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false);
}
}
} else if (subscribedToLiveUpdates) {
subscriptionCancelledTime.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
}
lastValidationCheckTime = System.currentTimeMillis();
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE")
+ " live updates purchased.");
OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true);
List<InAppPurchaseData> tokensToSend = new ArrayList<>();
if (liveUpdatesPurchases.size() > 0) {
List<String> tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";"));
for (InAppPurchaseData purchase : liveUpdatesPurchases) {
if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get()))
&& !Algorithms.isEmpty(purchase.getDeveloperPayload())) {
String payload = purchase.getDeveloperPayload();
if (!Algorithms.isEmpty(payload)) {
String[] arr = payload.split(" ");
if (arr.length > 0) {
settings.BILLING_USER_ID.set(arr[0]);
}
if (arr.length > 1) {
token = arr[1];
settings.BILLING_USER_TOKEN.set(token);
}
}
}
if (!tokensSent.contains(purchase.getProductId())) {
tokensToSend.add(purchase);
}
}
}
List<PurchaseInfo> purchaseInfoList = new ArrayList<>();
for (InAppPurchaseData purchase : tokensToSend) {
purchaseInfoList.add(getPurchaseInfo(purchase));
}
onSkuDetailsResponseDone(purchaseInfoList);
}
};
}
private IapClient getIapClient() {
return Iap.getIapClient((Activity) uiActivity);
}
// Call when a purchase is finished
private void onPurchaseFinished(InAppPurchaseData purchase) {
logDebug("Purchase finished: " + purchase.getProductId());
onPurchaseDone(getPurchaseInfo(purchase));
}
@Override
protected boolean isBillingManagerExists() {
return false;
}
@Override
protected void destroyBillingManager() {
// non implemented
}
@Override
public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == Constants.REQ_CODE_BUY_SUB) {
boolean succeed = false;
if (resultCode == Activity.RESULT_OK) {
PurchaseResultInfo result = SubscriptionUtils.getPurchaseResult(activity, data);
if (result != null) {
switch (result.getReturnCode()) {
case OrderStatusCode.ORDER_STATE_CANCEL:
logDebug("Purchase cancelled");
break;
case OrderStatusCode.ORDER_STATE_FAILED:
inventoryRequestPending = true;
logDebug("Purchase failed");
break;
case OrderStatusCode.ORDER_PRODUCT_OWNED:
inventoryRequestPending = true;
logDebug("Product already owned");
break;
case OrderStatusCode.ORDER_STATE_SUCCESS:
inventoryRequestPending = true;
InAppPurchaseData purchaseData = SubscriptionUtils.getInAppPurchaseData(null,
result.getInAppPurchaseData(), result.getInAppDataSignature());
if (purchaseData != null) {
onPurchaseFinished(purchaseData);
succeed = true;
} else {
logDebug("Purchase failed");
}
break;
default:
break;
}
} else {
logDebug("Purchase failed");
}
} else {
logDebug("Purchase cancelled");
}
if (!succeed) {
stop(true);
}
return true;
} else if (requestCode == Constants.REQ_CODE_BUY_INAPP) {
boolean succeed = false;
if (data == null) {
logDebug("data is null");
} else {
PurchaseResultInfo buyResultInfo = Iap.getIapClient(activity).parsePurchaseResultInfoFromIntent(data);
switch (buyResultInfo.getReturnCode()) {
case OrderStatusCode.ORDER_STATE_CANCEL:
logDebug("Order has been canceled");
break;
case OrderStatusCode.ORDER_STATE_FAILED:
inventoryRequestPending = true;
logDebug("Order has been failed");
break;
case OrderStatusCode.ORDER_PRODUCT_OWNED:
inventoryRequestPending = true;
logDebug("Product already owned");
break;
case OrderStatusCode.ORDER_STATE_SUCCESS:
InAppPurchaseData purchaseData = InAppUtils.getInAppPurchaseData(null,
buyResultInfo.getInAppPurchaseData(), buyResultInfo.getInAppDataSignature());
if (purchaseData != null) {
onPurchaseFinished(purchaseData);
succeed = true;
} else {
logDebug("Purchase failed");
}
break;
default:
break;
}
}
if (!succeed) {
stop(true);
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,196 @@
package net.osmand.plus.inapp;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.huawei.hms.iap.entity.ProductInfo;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
public class InAppPurchasesImpl extends InAppPurchases {
private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion();
private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree();
private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree();
private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{
new InAppPurchaseLiveUpdatesMonthlyFree(),
new InAppPurchaseLiveUpdates3MonthsFree(),
new InAppPurchaseLiveUpdatesAnnualFree()
};
public InAppPurchasesImpl(OsmandApplication ctx) {
super(ctx);
fullVersion = FULL_VERSION;
depthContours = DEPTH_CONTOURS_FREE;
contourLines = CONTOUR_LINES_FREE;
inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines };
liveUpdates = new LiveUpdatesInAppPurchasesFree();
for (InAppSubscription s : liveUpdates.getAllSubscriptions()) {
if (s instanceof InAppPurchaseLiveUpdatesMonthly) {
if (s.isDiscounted()) {
discountedMonthlyLiveUpdates = s;
} else {
monthlyLiveUpdates = s;
}
}
}
}
@Override
public boolean isFullVersion(String sku) {
return FULL_VERSION.getSku().equals(sku);
}
@Override
public boolean isDepthContours(String sku) {
return DEPTH_CONTOURS_FREE.getSku().equals(sku);
}
@Override
public boolean isContourLines(String sku) {
return CONTOUR_LINES_FREE.getSku().equals(sku);
}
@Override
public boolean isLiveUpdates(String sku) {
for (InAppPurchase p : LIVE_UPDATES_FREE) {
if (p.getSku().equals(sku)) {
return true;
}
}
return false;
}
private static class InAppPurchaseFullVersion extends InAppPurchase {
private static final String SKU_FULL_VERSION_PRICE = "net.osmand.huawei.full";
InAppPurchaseFullVersion() {
super(SKU_FULL_VERSION_PRICE);
}
@Override
public String getDefaultPrice(Context ctx) {
return ctx.getString(R.string.full_version_price);
}
}
private static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours {
private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.huawei.seadepth";
InAppPurchaseDepthContoursFree() {
super(SKU_DEPTH_CONTOURS_FREE);
}
}
private static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines {
private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.huawei.contourlines";
InAppPurchaseContourLinesFree() {
super(SKU_CONTOUR_LINES_FREE);
}
}
private static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly {
private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.huawei.monthly";
InAppPurchaseLiveUpdatesMonthlyFree() {
super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1);
}
private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null;
}
}
private static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months {
private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.huawei.3months";
InAppPurchaseLiveUpdates3MonthsFree() {
super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1);
}
private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null;
}
}
private static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual {
private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.huawei.annual";
InAppPurchaseLiveUpdatesAnnualFree() {
super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1);
}
private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null;
}
}
public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription {
private ProductInfo info;
InAppPurchaseLiveUpdatesOldSubscription(@NonNull ProductInfo info) {
super(info.getProductId(), true);
this.info = info;
}
@Override
public String getDefaultPrice(Context ctx) {
return "";
}
@Override
public CharSequence getTitle(Context ctx) {
return info.getProductName();
}
@Override
public CharSequence getDescription(@NonNull Context ctx) {
return info.getProductDesc();
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return null;
}
}
private static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList {
public LiveUpdatesInAppPurchasesFree() {
super(LIVE_UPDATES_FREE);
}
}
}

View file

@ -0,0 +1,49 @@
package net.osmand.plus.inapp;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.huawei.hms.iap.entity.InAppPurchaseData;
import com.huawei.hms.iap.entity.OwnedPurchasesResult;
import org.json.JSONException;
public class InAppUtils {
private static final String TAG = "InAppUtils";
@Nullable
public static InAppPurchaseData getPurchaseData(OwnedPurchasesResult result, String productId) {
if (result == null || result.getInAppPurchaseDataList() == null) {
Log.i(TAG, "result is null");
return null;
}
int index = result.getItemList().indexOf(productId);
if (index != -1) {
String data = result.getInAppPurchaseDataList().get(index);
String signature = result.getInAppSignature().get(index);
return getInAppPurchaseData(productId, data, signature);
}
return null;
}
@Nullable
public static InAppPurchaseData getInAppPurchaseData(@Nullable String productId, @NonNull String data, @NonNull String signature) {
if (CipherUtil.doCheck(data, signature, CipherUtil.getPublicKey())) {
try {
InAppPurchaseData purchaseData = new InAppPurchaseData(data);
if (purchaseData.getPurchaseState() == InAppPurchaseData.PurchaseState.PURCHASED) {
if (productId == null || productId.equals(purchaseData.getProductId())) {
return purchaseData;
}
}
} catch (JSONException e) {
Log.e(TAG, "delivery: " + e.getMessage());
}
} else {
Log.e(TAG, "delivery: verify signature error");
}
return null;
}
}

View file

@ -0,0 +1,138 @@
/**
* Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.osmand.plus.inapp;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.huawei.hms.iap.Iap;
import com.huawei.hms.iap.entity.InAppPurchaseData;
import com.huawei.hms.iap.entity.OrderStatusCode;
import com.huawei.hms.iap.entity.OwnedPurchasesResult;
import com.huawei.hms.iap.entity.PurchaseResultInfo;
import org.json.JSONException;
import java.util.List;
/**
* Util for Subscription function.
*
* @since 2019/12/9
*/
public class SubscriptionUtils {
private static final String TAG = "SubscriptionUtils";
/**
* Decide whether to offer subscription service
*
* @param result the OwnedPurchasesResult from IapClient.obtainOwnedPurchases
* @param productId subscription product id
* @return decision result
*/
@Nullable
public static InAppPurchaseData getPurchaseData(OwnedPurchasesResult result, String productId) {
if (null == result) {
Log.e(TAG, "OwnedPurchasesResult is null");
return null;
}
List<String> dataList = result.getInAppPurchaseDataList();
List<String> signatureList = result.getInAppSignature();
for (int i = 0; i < dataList.size(); i++) {
String data = dataList.get(i);
String signature = signatureList.get(i);
InAppPurchaseData purchaseData = getInAppPurchaseData(productId, data, signature);
if (purchaseData != null) {
return purchaseData;
}
}
return null;
}
@Nullable
public static InAppPurchaseData getInAppPurchaseData(@Nullable String productId, @NonNull String data, @NonNull String signature) {
try {
InAppPurchaseData purchaseData = new InAppPurchaseData(data);
if (productId == null || productId.equals(purchaseData.getProductId())) {
boolean credible = CipherUtil.doCheck(data, signature, CipherUtil.getPublicKey());
if (credible) {
return purchaseData.isSubValid() ? purchaseData : null;
} else {
Log.e(TAG, "check the data signature fail");
return null;
}
}
} catch (JSONException e) {
Log.e(TAG, "parse InAppPurchaseData JSONException", e);
return null;
}
return null;
}
/**
* Parse PurchaseResult data from intent
*
* @param activity Activity
* @param data the intent from onActivityResult
* @return PurchaseResultInfo
*/
public static PurchaseResultInfo getPurchaseResult(Activity activity, Intent data) {
PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(activity).parsePurchaseResultInfoFromIntent(data);
if (null == purchaseResultInfo) {
Log.e(TAG, "PurchaseResultInfo is null");
} else {
int returnCode = purchaseResultInfo.getReturnCode();
String errMsg = purchaseResultInfo.getErrMsg();
switch (returnCode) {
case OrderStatusCode.ORDER_PRODUCT_OWNED:
Log.w(TAG, "you have owned this product");
break;
case OrderStatusCode.ORDER_STATE_SUCCESS:
boolean credible = CipherUtil.doCheck(purchaseResultInfo.getInAppPurchaseData(), purchaseResultInfo.getInAppDataSignature(), CipherUtil
.getPublicKey());
if (credible) {
try {
InAppPurchaseData inAppPurchaseData = new InAppPurchaseData(purchaseResultInfo.getInAppPurchaseData());
if (!inAppPurchaseData.isSubValid()) {
return getFailedPurchaseResultInfo();
}
} catch (JSONException e) {
Log.e(TAG, "parse InAppPurchaseData JSONException", e);
return getFailedPurchaseResultInfo();
}
} else {
Log.e(TAG, "check the data signature fail");
return getFailedPurchaseResultInfo();
}
default:
Log.e(TAG, "returnCode: " + returnCode + " , errMsg: " + errMsg);
break;
}
}
return purchaseResultInfo;
}
private static PurchaseResultInfo getFailedPurchaseResultInfo() {
PurchaseResultInfo info = new PurchaseResultInfo();
info.setReturnCode(OrderStatusCode.ORDER_STATE_FAILED);
return info;
}
}

View file

@ -38,7 +38,7 @@ import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
import net.osmand.plus.helpers.AvoidSpecificRoads;
import net.osmand.plus.helpers.LockHelper;
import net.osmand.plus.helpers.WaypointHelper;
import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.inapp.InAppPurchaseHelperImpl;
import net.osmand.plus.liveupdates.LiveUpdatesHelper;
import net.osmand.plus.mapmarkers.MapMarkersDbHelper;
import net.osmand.plus.monitoring.LiveMonitoringHelper;
@ -428,7 +428,7 @@ public class AppInitializer implements IProgress {
}
getLazyRoutingConfig();
app.applyTheme(app);
app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelper(app), InAppPurchaseHelper.class);
app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelperImpl(app), InAppPurchaseHelperImpl.class);
app.poiTypes = startupInit(MapPoiTypes.getDefaultNoInit(), MapPoiTypes.class);
app.transportRoutingHelper = startupInit(new TransportRoutingHelper(app), TransportRoutingHelper.class);
app.routingHelper = startupInit(new RoutingHelper(app), RoutingHelper.class);

View file

@ -34,6 +34,7 @@ public class ContextMenuItem {
private boolean hidden;
private int order;
private String description;
private final OnUpdateCallback onUpdateCallback;
private final ContextMenuAdapter.ItemClickListener itemClickListener;
private final ContextMenuAdapter.OnIntegerValueChangedListener integerListener;
private final ContextMenuAdapter.ProgressListener progressListener;
@ -58,6 +59,7 @@ public class ContextMenuItem {
boolean skipPaintingWithoutColor,
int order,
String description,
OnUpdateCallback onUpdateCallback,
ContextMenuAdapter.ItemClickListener itemClickListener,
ContextMenuAdapter.OnIntegerValueChangedListener integerListener,
ContextMenuAdapter.ProgressListener progressListener,
@ -81,6 +83,7 @@ public class ContextMenuItem {
this.skipPaintingWithoutColor = skipPaintingWithoutColor;
this.order = order;
this.description = description;
this.onUpdateCallback = onUpdateCallback;
this.itemClickListener = itemClickListener;
this.integerListener = integerListener;
this.progressListener = progressListener;
@ -245,6 +248,16 @@ public class ContextMenuItem {
return id;
}
public void update() {
if (onUpdateCallback != null) {
onUpdateCallback.onUpdateMenuItem(this);
}
}
public interface OnUpdateCallback {
void onUpdateMenuItem(ContextMenuItem item);
}
public static ItemBuilder createBuilder(String title) {
return new ItemBuilder().setTitle(title);
}
@ -268,6 +281,7 @@ public class ContextMenuItem {
private boolean mIsClickable = true;
private int mOrder = 0;
private String mDescription = null;
private OnUpdateCallback mOnUpdateCallback = null;
private ContextMenuAdapter.ItemClickListener mItemClickListener = null;
private ContextMenuAdapter.OnIntegerValueChangedListener mIntegerListener = null;
private ContextMenuAdapter.ProgressListener mProgressListener = null;
@ -348,6 +362,11 @@ public class ContextMenuItem {
return this;
}
public ItemBuilder setOnUpdateCallback(OnUpdateCallback onUpdateCallback) {
mOnUpdateCallback = onUpdateCallback;
return this;
}
public ItemBuilder setListener(ContextMenuAdapter.ItemClickListener checkBoxListener) {
mItemClickListener = checkBoxListener;
return this;
@ -403,10 +422,12 @@ public class ContextMenuItem {
}
public ContextMenuItem createItem() {
return new ContextMenuItem(mTitleId, mTitle, mIcon, mColorRes, mSecondaryIcon,
ContextMenuItem item = new ContextMenuItem(mTitleId, mTitle, mIcon, mColorRes, mSecondaryIcon,
mSelected, mProgress, mLayout, mLoading, mIsCategory, mIsClickable, mSkipPaintingWithoutColor,
mOrder, mDescription, mItemClickListener, mIntegerListener, mProgressListener, mItemDeleteAction,
mHideDivider, mHideCompoundButton, mMinHeight, mTag, mId);
mOrder, mDescription, mOnUpdateCallback, mItemClickListener, mIntegerListener, mProgressListener,
mItemDeleteAction, mHideDivider, mHideCompoundButton, mMinHeight, mTag, mId);
item.update();
return item;
}
}
}

View file

@ -1,66 +0,0 @@
package net.osmand.plus;
import android.app.Activity;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HuaweiDrmHelper {
private static final String TAG = HuaweiDrmHelper.class.getSimpleName();
//Copyright protection id
private static final String DRM_ID = "101117397";
//Copyright protection public key
private static final String DRM_PUBLIC_KEY = "9d6f861e7d46be167809a6a62302749a6753b3c1bd02c9729efb3973e268091d";
public static void check(Activity activity) {
boolean succeed = false;
try {
final WeakReference<Activity> activityRef = new WeakReference<>(activity);
Class<?> drmCheckCallbackClass = Class.forName("com.huawei.android.sdk.drm.DrmCheckCallback");
Object callback = java.lang.reflect.Proxy.newProxyInstance(
drmCheckCallbackClass.getClassLoader(),
new java.lang.Class[]{drmCheckCallbackClass},
new java.lang.reflect.InvocationHandler() {
@Override
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) {
Activity a = activityRef.get();
if (a != null && !a.isFinishing()) {
String method_name = method.getName();
if (method_name.equals("onCheckSuccess")) {
// skip now
} else if (method_name.equals("onCheckFailed")) {
closeApplication(a);
}
}
return null;
}
});
Class<?> drmClass = Class.forName("com.huawei.android.sdk.drm.Drm");
Class[] partypes = new Class[]{Activity.class, String.class, String.class, String.class, drmCheckCallbackClass};
Method check = drmClass.getMethod("check", partypes);
check.invoke(null, activity, activity.getPackageName(), DRM_ID, DRM_PUBLIC_KEY, callback);
succeed = true;
} catch (ClassNotFoundException e) {
Log.e(TAG, "check: ", e);
} catch (NoSuchMethodException e) {
Log.e(TAG, "check: ", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "check: ", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "check: ", e);
}
if (!succeed) {
closeApplication(activity);
}
}
private static void closeApplication(Activity activity) {
((OsmandApplication) activity.getApplication()).closeApplicationAnywayImpl(activity, true);
}
}

View file

@ -121,8 +121,8 @@ public class Version {
public static boolean isFreeVersion(OsmandApplication ctx){
return ctx.getPackageName().equals(FREE_VERSION_NAME) ||
ctx.getPackageName().equals(FREE_DEV_VERSION_NAME) ||
ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME)
;
ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) ||
isHuawei(ctx);
}
public static boolean isPaidVersion(OsmandApplication ctx) {

View file

@ -67,7 +67,6 @@ import net.osmand.plus.AppInitializer;
import net.osmand.plus.AppInitializer.AppInitializeListener;
import net.osmand.plus.AppInitializer.InitEvents;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.HuaweiDrmHelper;
import net.osmand.plus.MapMarkersHelper.MapMarker;
import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener;
import net.osmand.plus.OnDismissDialogFragmentListener;
@ -276,9 +275,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
super.onCreate(savedInstanceState);
if (Version.isHuawei(getMyApplication())) {
HuaweiDrmHelper.check(this);
}
// Full screen is not used here
// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
@ -1466,6 +1462,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
}
protected void onPostExecute(Void result) {
DashboardOnMap dashboard = getDashboard();
if (dashboard != null) {
dashboard.onMapSettingsUpdated();
}
}
}.executeOnExecutor(singleThreadExecutor, (Void) null);

View file

@ -899,7 +899,7 @@ public class MapActivityActions implements DialogProvider {
}
}).createItem());
if (Version.isGooglePlayEnabled(app) || Version.isDeveloperVersion(app)) {
if (Version.isGooglePlayEnabled(app) || Version.isHuawei(app) || Version.isDeveloperVersion(app)) {
optionsMenuHelper.addItem(new ItemBuilder().setTitleId(R.string.osm_live, mapActivity)
.setId(DRAWER_OSMAND_LIVE_ID)
.setIcon(R.drawable.ic_action_osm_live)

View file

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -14,12 +13,15 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.download.DownloadActivity;
import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseInitCallback;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType;
import net.osmand.plus.liveupdates.OsmLiveRestartBottomSheetDialogFragment;
@ -27,6 +29,7 @@ import net.osmand.plus.srtmplugin.SRTMPlugin;
import org.apache.commons.logging.Log;
import java.lang.ref.WeakReference;
import java.util.List;
@SuppressLint("Registered")
@ -34,14 +37,7 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
private static final Log LOG = PlatformUtil.getLog(OsmandInAppPurchaseActivity.class);
private InAppPurchaseHelper purchaseHelper;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isInAppPurchaseAllowed() && isInAppPurchaseSupported()) {
purchaseHelper = getMyApplication().getInAppPurchaseHelper();
}
}
private boolean activityDestroyed;
@Override
protected void onResume() {
@ -53,19 +49,41 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
protected void onDestroy() {
super.onDestroy();
deinitInAppPurchaseHelper();
activityDestroyed = true;
}
private void initInAppPurchaseHelper() {
deinitInAppPurchaseHelper();
if (purchaseHelper == null) {
OsmandApplication app = getMyApplication();
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (app.getSettings().isInternetConnectionAvailable()
&& isInAppPurchaseAllowed()
&& isInAppPurchaseSupported(purchaseHelper)) {
this.purchaseHelper = purchaseHelper;
}
}
if (purchaseHelper != null) {
purchaseHelper.setUiActivity(this);
final WeakReference<OsmandInAppPurchaseActivity> activityRef = new WeakReference<>(this);
purchaseHelper.isInAppPurchaseSupported(this, new InAppPurchaseInitCallback() {
@Override
public void onSuccess() {
OsmandInAppPurchaseActivity activity = activityRef.get();
if (!activityDestroyed && AndroidUtils.isActivityNotDestroyed(activity)) {
purchaseHelper.setUiActivity(activity);
if (purchaseHelper.needRequestInventory()) {
purchaseHelper.requestInventory();
}
}
}
@Override
public void onFail() {
}
});
}
}
private void deinitInAppPurchaseHelper() {
if (purchaseHelper != null) {
purchaseHelper.resetUiActivity(this);
@ -80,7 +98,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (purchaseHelper != null) {
app.logEvent("in_app_purchase_redirect");
try {
purchaseHelper.purchaseFullVersion(activity);
} catch (UnsupportedOperationException e) {
LOG.error("purchaseFullVersion is not supported", e);
}
}
} else {
app.logEvent("paid_version_redirect");
@ -101,18 +123,27 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (purchaseHelper != null) {
app.logEvent("depth_contours_purchase_redirect");
try {
purchaseHelper.purchaseDepthContours(activity);
} catch (UnsupportedOperationException e) {
LOG.error("purchaseDepthContours is not supported", e);
}
}
}
}
public static void purchaseSrtmPlugin(@NonNull final Activity activity) {
OsmandPlugin plugin = OsmandPlugin.getPlugin(SRTMPlugin.class);
if(plugin == null || plugin.getInstallURL() == null) {
Toast.makeText(activity.getApplicationContext(),
activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show();
} else {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL())));
public static void purchaseContourLines(@NonNull final Activity activity) {
OsmandApplication app = (OsmandApplication) activity.getApplication();
if (app != null) {
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (purchaseHelper != null) {
app.logEvent("contour_lines_purchase_redirect");
try {
purchaseHelper.purchaseContourLines(activity);
} catch (UnsupportedOperationException e) {
LOG.error("purchaseContourLines is not supported", e);
}
}
}
}
@ -129,8 +160,9 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
return false;
}
public boolean isInAppPurchaseSupported() {
return Version.isGooglePlayEnabled(getMyApplication());
public boolean isInAppPurchaseSupported(InAppPurchaseHelper purchaseHelper) {
OsmandApplication app = getMyApplication();
return Version.isGooglePlayEnabled(app) || Version.isHuawei(app);
}
@Override
@ -178,6 +210,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
}
onInAppPurchaseItemPurchased(sku);
fireInAppPurchaseItemPurchasedOnFragments(fragmentManager, sku, active);
if (purchaseHelper != null && purchaseHelper.getContourLines().getSku().equals(sku)) {
if (!(this instanceof MapActivity)) {
finish();
}
}
}
public void fireInAppPurchaseItemPurchasedOnFragments(@NonNull FragmentManager fragmentManager,
@ -222,6 +259,17 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
boolean handled = false;
if (purchaseHelper != null) {
handled = purchaseHelper.onActivityResult(this, requestCode, resultCode, data);
}
if (!handled) {
super.onActivityResult(requestCode, resultCode, data);
}
}
public void onInAppPurchaseError(InAppPurchaseTaskType taskType, String error) {
// not implemented
}

View file

@ -428,7 +428,7 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
buttonCancelView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
manageSubscription(ctx, s.getSku());
purchaseHelper.manageSubscription(ctx, s.getSku());
}
});
div.setVisibility(View.VISIBLE);
@ -538,15 +538,6 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
}
}
private void manageSubscription(@NonNull Context ctx, @Nullable String sku) {
String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName();
if (!Algorithms.isEmpty(sku)) {
url += "&sku=" + sku;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
private ViewGroup buildPlanTypeCard(@NonNull Context ctx, ViewGroup container) {
if (getPlanTypeFeatures().length == 0) {
return null;

View file

@ -79,7 +79,7 @@ public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanDialogFragm
public void onClick(View v) {
Activity activity = getActivity();
if (activity != null) {
OsmandInAppPurchaseActivity.purchaseSrtmPlugin(activity);
OsmandInAppPurchaseActivity.purchaseContourLines(activity);
dismiss();
}
}

View file

@ -568,7 +568,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo
boolean refresh = this.visibleType == type && !appModeChanged;
previousAppMode = currentAppMode;
this.visibleType = type;
visibleType = type;
DashboardOnMap.staticVisible = visible;
DashboardOnMap.staticVisibleType = type;
mapActivity.enableDrawer();
@ -1032,6 +1032,24 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo
}
}
public void onMapSettingsUpdated() {
if (DashboardType.CONFIGURE_MAP.equals(visibleType)) {
updateMenuItems();
}
}
public void updateMenuItems() {
if (listAdapter != null) {
for (int i = 0; i < listAdapter.getCount(); i++) {
Object o = listAdapter.getItem(i);
if (o instanceof ContextMenuItem) {
((ContextMenuItem) o).update();
}
}
listAdapter.notifyDataSetChanged();
}
}
public void updateLocation(final boolean centerChanged, final boolean locationChanged,
final boolean compassChanged) {
if (inLocationUpdate) {

View file

@ -277,10 +277,12 @@ public class ConfigureMapMenu {
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_map_rendering, activity)
.setId(MAP_RENDERING_CATEGORY_ID)
.setCategory(true).setLayout(R.layout.list_group_title_with_switch).createItem());
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_renderer, activity)
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setId(MAP_STYLE_ID)
.setDescription(getRenderDescr(activity)).setLayout(R.layout.list_item_single_line_descrition_narrow)
.setIcon(R.drawable.ic_map).setListener(new ContextMenuAdapter.ItemClickListener() {
.setTitleId(R.string.map_widget_renderer, activity)
.setLayout(R.layout.list_item_single_line_descrition_narrow)
.setIcon(R.drawable.ic_map)
.setListener(new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(final ArrayAdapter<ContextMenuItem> ad, int itemId,
final int pos, boolean isChecked, int[] viewCoordinates) {
@ -290,6 +292,13 @@ public class ConfigureMapMenu {
}
})
.setItemDeleteAction(makeDeleteAction(settings.RENDERER))
.setOnUpdateCallback(new ContextMenuItem.OnUpdateCallback() {
@Override
public void onUpdateMenuItem(ContextMenuItem item) {
String renderDescr = getRenderDescr(app);
item.setDescription(renderDescr);
}
})
.createItem());
String description = "";
@ -942,13 +951,13 @@ public class ConfigureMapMenu {
dialog.show();
}
protected String getRenderDescr(final MapActivity activity) {
RendererRegistry rr = activity.getMyApplication().getRendererRegistry();
protected String getRenderDescr(OsmandApplication app) {
RendererRegistry rr = app.getRendererRegistry();
RenderingRulesStorage storage = rr.getCurrentSelectedRenderer();
if (storage == null) {
return "";
}
String translation = RendererRegistry.getTranslatedRendererName(activity, storage.getName());
String translation = RendererRegistry.getTranslatedRendererName(app, storage.getName());
return translation == null ? storage.getName() : translation;
}

View file

@ -20,12 +20,14 @@ import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import net.osmand.AndroidNetworkUtils;
import net.osmand.PlatformUtil;
import net.osmand.osm.AbstractPoiType;
import net.osmand.osm.MapPoiTypes;
import net.osmand.osm.PoiCategory;
import net.osmand.osm.PoiType;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.activities.OsmandInAppPurchaseActivity;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.Version;
@ -56,7 +58,7 @@ import java.util.Map;
public class DiscountHelper {
private static final String TAG = "DiscountHelper";
//private static final String DISCOUNT_JSON = "discount.json";
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(DiscountHelper.class);
private static long mLastCheckTime;
private static ControllerData mData;
@ -81,7 +83,7 @@ public class DiscountHelper {
public static void checkAndDisplay(final MapActivity mapActivity) {
OsmandApplication app = mapActivity.getMyApplication();
OsmandSettings settings = app.getSettings();
if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get() || Version.isHuawei(app)) {
if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get()) {
return;
}
if (mBannerVisible) {
@ -312,7 +314,11 @@ public class DiscountHelper {
if (purchaseHelper != null) {
if (url.contains(purchaseHelper.getFullVersion().getSku())) {
app.logEvent("in_app_purchase_redirect");
try {
purchaseHelper.purchaseFullVersion(mapActivity);
} catch (UnsupportedOperationException e) {
LOG.error("purchaseFullVersion is not supported", e);
}
} else {
for (InAppPurchase p : purchaseHelper.getLiveUpdates().getAllSubscriptions()) {
if (url.contains(p.getSku())) {

View file

@ -2,6 +2,8 @@ package net.osmand.plus.inapp;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
@ -9,33 +11,21 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient.BillingResponseCode;
import com.android.billingclient.api.BillingClient.SkuType;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsResponseListener;
import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.AndroidNetworkUtils.OnRequestsResultListener;
import net.osmand.AndroidNetworkUtils.RequestResponse;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener;
import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms;
import org.json.JSONArray;
@ -43,7 +33,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -53,54 +42,31 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
public class InAppPurchaseHelper {
public abstract class InAppPurchaseHelper {
// Debug tag, for logging
private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class);
protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class);
private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
private boolean mDebugLog = false;
public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
private InAppPurchases purchases;
private long lastValidationCheckTime;
private boolean inventoryRequested;
protected InAppPurchases purchases;
protected long lastValidationCheckTime;
protected boolean inventoryRequested;
private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily
// (arbitrary) request code for the purchase flow
private static final int RC_REQUEST = 10001;
// The helper object
private BillingManager billingManager;
private List<SkuDetails> skuDetailsList;
protected boolean isDeveloperVersion;
protected String token = "";
protected InAppPurchaseTaskType activeTask;
protected boolean processingTask = false;
protected boolean inventoryRequestPending = false;
private boolean isDeveloperVersion;
private String token = "";
private InAppPurchaseTaskType activeTask;
private boolean processingTask = false;
private boolean inventoryRequestPending = false;
private OsmandApplication ctx;
private InAppPurchaseListener uiActivity = null;
/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
* (that you got from the Google Play developer console). This is not your
* developer public key, it's the *app-specific* public key.
*
* Instead of just storing the entire literal string here embedded in the
* program, construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key. The key itself is not secret information, but we don't
* want to make it easy for an attacker to replace the public key with one
* of their own and then fake messages from the server.
*/
private static final String BASE64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" +
"UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" +
"wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" +
"OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" +
"I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" +
"YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB";
protected OsmandApplication ctx;
protected InAppPurchaseListener uiActivity = null;
public interface InAppPurchaseListener {
void onError(InAppPurchaseTaskType taskType, String error);
void onGetItems();
@ -112,16 +78,62 @@ public class InAppPurchaseHelper {
void dismissProgress(InAppPurchaseTaskType taskType);
}
public interface InAppPurchaseInitCallback {
void onSuccess();
void onFail();
}
public enum InAppPurchaseTaskType {
REQUEST_INVENTORY,
PURCHASE_FULL_VERSION,
PURCHASE_LIVE_UPDATES,
PURCHASE_DEPTH_CONTOURS
PURCHASE_DEPTH_CONTOURS,
PURCHASE_CONTOUR_LINES
}
public interface InAppRunnable {
public abstract class InAppCommand {
InAppCommandResultHandler resultHandler;
// return true if done and false if async task started
boolean run(InAppPurchaseHelper helper);
abstract void run(InAppPurchaseHelper helper);
protected void commandDone() {
InAppCommandResultHandler resultHandler = this.resultHandler;
if (resultHandler != null) {
resultHandler.onCommandDone(this);
}
}
}
public interface InAppCommandResultHandler {
void onCommandDone(@NonNull InAppCommand command);
}
public static class PurchaseInfo {
private String sku;
private String orderId;
private String purchaseToken;
public PurchaseInfo(String sku, String orderId, String purchaseToken) {
this.sku = sku;
this.orderId = orderId;
this.purchaseToken = purchaseToken;
}
public String getSku() {
return sku;
}
public String getOrderId() {
return orderId;
}
public String getPurchaseToken() {
return purchaseToken;
}
}
public String getToken() {
@ -144,6 +156,10 @@ public class InAppPurchaseHelper {
return Version.isDeveloperBuild(ctx) || ctx.getSettings().DEPTH_CONTOURS_PURCHASED.get();
}
public static boolean isContourLinesPurchased(@NonNull OsmandApplication ctx) {
return Version.isDeveloperBuild(ctx) || ctx.getSettings().CONTOUR_LINES_PURCHASED.get();
}
public InAppPurchases getInAppPurchases() {
return purchases;
}
@ -176,9 +192,10 @@ public class InAppPurchaseHelper {
public InAppPurchaseHelper(OsmandApplication ctx) {
this.ctx = ctx;
isDeveloperVersion = Version.isDeveloperVersion(ctx);
purchases = new InAppPurchases(ctx);
}
public abstract void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback);
public boolean hasInventory() {
return lastValidationCheckTime != 0;
}
@ -194,12 +211,8 @@ public class InAppPurchaseHelper {
return false;
}
private BillingManager getBillingManager() {
return billingManager;
}
private void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) {
if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) {
protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppCommand command) {
if (isDeveloperVersion || (!Version.isGooglePlayEnabled(ctx) && !Version.isHuawei(ctx))) {
notifyDismissProgress(taskType);
stop(true);
return;
@ -222,117 +235,21 @@ public class InAppPurchaseHelper {
try {
processingTask = true;
activeTask = taskType;
billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingUpdatesListener() {
command.resultHandler = new InAppCommandResultHandler() {
@Override
public void onBillingClientSetupFinished() {
logDebug("Setup finished.");
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
public void onCommandDone(@NonNull InAppCommand command) {
processingTask = false;
}
if (!billingManager.isServiceConnected()) {
// Oh noes, there was a problem.
//complain("Problem setting up in-app billing: " + result);
notifyError(taskType, billingManager.getBillingClientResponseMessage());
stop(true);
return;
}
processingTask = !runnable.run(InAppPurchaseHelper.this);
}
@Override
public void onConsumeFinished(String token, BillingResult billingResult) {
}
@Override
public void onPurchasesUpdated(final List<Purchase> purchases) {
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) {
List<String> skuInApps = new ArrayList<>();
for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) {
skuInApps.add(purchase.getSku());
}
for (Purchase p : purchases) {
skuInApps.add(p.getSku());
}
billingManager.querySkuDetailsAsync(SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListInApps) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query inapps sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<String> skuSubscriptions = new ArrayList<>();
for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) {
skuSubscriptions.add(subscription.getSku());
}
for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku());
}
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
if (billingManager == null) {
stop(true);
return;
}
billingManager.querySkuDetailsAsync(SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, final List<SkuDetails> skuDetailsListSubscriptions) {
// Is it a failure?
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
List<SkuDetails> skuDetailsList = new ArrayList<>(skuDetailsListInApps);
skuDetailsList.addAll(skuDetailsListSubscriptions);
InAppPurchaseHelper.this.skuDetailsList = skuDetailsList;
mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList);
}
});
}
});
}
for (Purchase purchase : purchases) {
if (!purchase.isAcknowledged()) {
onPurchaseFinished(purchase);
}
}
}
@Override
public void onPurchaseCanceled() {
stop(true);
}
});
};
execImpl(taskType, command);
} catch (Exception e) {
logError("exec Error", e);
stop(true);
}
}
protected abstract void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand command);
public boolean needRequestInventory() {
return !inventoryRequested && ((isSubscribedToLiveUpdates(ctx) && Algorithms.isEmpty(ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get()))
|| System.currentTimeMillis() - lastValidationCheckTime > PURCHASE_VALIDATION_PERIOD_MSEC);
@ -343,322 +260,20 @@ public class InAppPurchaseHelper {
new RequestInventoryTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
public void purchaseFullVersion(final Activity activity) {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION);
exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() {
@Override
public boolean run(InAppPurchaseHelper helper) {
try {
SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku());
if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false;
} catch (Exception e) {
complain("Cannot launch full version purchase!");
logError("purchaseFullVersion Error", e);
stop(true);
}
return true;
}
});
}
public abstract void purchaseFullVersion(@NonNull final Activity activity) throws UnsupportedOperationException;
public void purchaseLiveUpdates(Activity activity, String sku, String email, String userName,
public void purchaseLiveUpdates(@NonNull Activity activity, String sku, String email, String userName,
String countryDownloadName, boolean hideUserName) {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
new LiveUpdatesPurchaseTask(activity, sku, email, userName, countryDownloadName, hideUserName)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
public void purchaseDepthContours(final Activity activity) {
notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS);
exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() {
@Override
public boolean run(InAppPurchaseHelper helper) {
try {
SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku());
if (skuDetails == null) {
throw new IllegalArgumentException("Cannot find sku details");
}
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.initiatePurchaseFlow(activity, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false;
} catch (Exception e) {
complain("Cannot launch depth contours purchase!");
logError("purchaseDepthContours Error", e);
stop(true);
}
return true;
}
});
}
public abstract void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException;
@Nullable
private SkuDetails getSkuDetails(@NonNull String sku) {
List<SkuDetails> skuDetailsList = this.skuDetailsList;
if (skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
if (details.getSku().equals(sku)) {
return details;
}
}
}
return null;
}
public abstract void purchaseContourLines(@NonNull final Activity activity) throws UnsupportedOperationException;
private boolean hasDetails(@NonNull String sku) {
return getSkuDetails(sku) != null;
}
@Nullable
private Purchase getPurchase(@NonNull String sku) {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
List<Purchase> purchases = billingManager.getPurchases();
if (purchases != null) {
for (Purchase p : purchases) {
if (p.getSku().equals(sku)) {
return p;
}
}
}
}
return null;
}
// Listener that's called when we finish querying the items and subscriptions we own
private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
@NonNull
private List<String> getAllOwnedSubscriptionSkus() {
List<String> result = new ArrayList<>();
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
for (Purchase p : billingManager.getPurchases()) {
if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) {
result.add(p.getSku());
}
}
}
return result;
}
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
logDebug("Query sku details finished.");
// Have we been disposed of in the meantime? If so, quit.
if (getBillingManager() == null) {
stop(true);
return;
}
// Is it a failure?
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
logError("Failed to query inventory: " + billingResult.getResponseCode());
notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage());
stop(true);
return;
}
logDebug("Query sku details was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
List<String> allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus();
for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
if (hasDetails(s.getSku())) {
Purchase purchase = getPurchase(s.getSku());
SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku());
if (liveUpdatesDetails != null) {
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
}
allOwnedSubscriptionSkus.remove(s.getSku());
}
}
for (String sku : allOwnedSubscriptionSkus) {
Purchase purchase = getPurchase(sku);
SkuDetails liveUpdatesDetails = getSkuDetails(sku);
if (liveUpdatesDetails != null) {
InAppSubscription s = getLiveUpdates().upgradeSubscription(sku);
if (s == null) {
s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails);
}
fetchInAppPurchase(s, liveUpdatesDetails, purchase);
}
}
InAppPurchase fullVersion = getFullVersion();
if (hasDetails(fullVersion.getSku())) {
Purchase purchase = getPurchase(fullVersion.getSku());
SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku());
if (fullPriceDetails != null) {
fetchInAppPurchase(fullVersion, fullPriceDetails, purchase);
}
}
InAppPurchase depthContours = getDepthContours();
if (hasDetails(depthContours.getSku())) {
Purchase purchase = getPurchase(depthContours.getSku());
SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku());
if (depthContoursDetails != null) {
fetchInAppPurchase(depthContours, depthContoursDetails, purchase);
}
}
InAppPurchase contourLines = getContourLines();
if (hasDetails(contourLines.getSku())) {
Purchase purchase = getPurchase(contourLines.getSku());
SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku());
if (contourLinesDetails != null) {
fetchInAppPurchase(contourLines, contourLinesDetails, purchase);
}
}
Purchase fullVersionPurchase = getPurchase(fullVersion.getSku());
boolean fullVersionPurchased = fullVersionPurchase != null;
if (fullVersionPurchased) {
ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
}
Purchase depthContoursPurchase = getPurchase(depthContours.getSku());
boolean depthContoursPurchased = depthContoursPurchase != null;
if (depthContoursPurchased) {
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
}
// Do we have the live updates?
boolean subscribedToLiveUpdates = false;
List<Purchase> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = getPurchase(p.getSku());
if (purchase != null) {
liveUpdatesPurchases.add(purchase);
if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true;
}
}
}
OsmandPreference<Long> subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME;
if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) {
if (subscriptionCancelledTime.get() == 0) {
subscriptionCancelledTime.set(System.currentTimeMillis());
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
} else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false);
}
}
} else if (subscribedToLiveUpdates) {
subscriptionCancelledTime.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
}
lastValidationCheckTime = System.currentTimeMillis();
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE")
+ " live updates purchased.");
OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true);
List<Purchase> tokensToSend = new ArrayList<>();
if (liveUpdatesPurchases.size() > 0) {
List<String> tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";"));
for (Purchase purchase : liveUpdatesPurchases) {
if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get()))
&& !Algorithms.isEmpty(purchase.getDeveloperPayload())) {
String payload = purchase.getDeveloperPayload();
if (!Algorithms.isEmpty(payload)) {
String[] arr = payload.split(" ");
if (arr.length > 0) {
settings.BILLING_USER_ID.set(arr[0]);
}
if (arr.length > 1) {
token = arr[1];
settings.BILLING_USER_TOKEN.set(token);
}
}
}
if (!tokensSent.contains(purchase.getSku())) {
tokensToSend.add(purchase);
}
}
}
final OnRequestResultListener listener = new OnRequestResultListener() {
@Override
public void onResult(String result) {
notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY);
notifyGetItems();
stop(true);
logDebug("Initial inapp query finished");
}
};
if (tokensToSend.size() > 0) {
sendTokens(tokensToSend, listener);
} else {
listener.onResult("OK");
}
}
};
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
if (purchase != null) {
inAppPurchase.setPurchaseState(PurchaseState.PURCHASED);
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime());
} else {
inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED);
}
inAppPurchase.setPrice(skuDetails.getPrice());
inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode());
if (skuDetails.getPriceAmountMicros() > 0) {
inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d);
}
String subscriptionPeriod = skuDetails.getSubscriptionPeriod();
if (!Algorithms.isEmpty(subscriptionPeriod)) {
if (inAppPurchase instanceof InAppSubscription) {
try {
((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod);
} catch (ParseException e) {
LOG.error(e);
}
}
}
if (inAppPurchase instanceof InAppSubscription) {
String introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try {
s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));
} catch (ParseException e) {
LOG.error(e);
}
}
}
}
public abstract void manageSubscription(@NonNull Context ctx, @Nullable String sku);
@SuppressLint("StaticFieldLeak")
private class LiveUpdatesPurchaseTask extends AsyncTask<Void, Void, String> {
@ -746,31 +361,7 @@ public class InAppPurchaseHelper {
if (!Algorithms.isEmpty(userId) && !Algorithms.isEmpty(token)) {
logDebug("Launching purchase flow for live updates subscription for userId=" + userId);
final String payload = userId + " " + token;
exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, new InAppRunnable() {
@Override
public boolean run(InAppPurchaseHelper helper) {
try {
Activity a = activity.get();
SkuDetails skuDetails = getSkuDetails(sku);
if (a != null && skuDetails != null) {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.setPayload(payload);
billingManager.initiatePurchaseFlow(a, skuDetails);
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false;
} else {
stop(true);
}
} catch (Exception e) {
logError("launchPurchaseFlow Error", e);
stop(true);
}
return true;
}
});
exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, getPurchaseLiveUpdatesCommand(activity, sku, payload));
} else {
notifyError(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, "Empty userId");
stop(true);
@ -778,6 +369,9 @@ public class InAppPurchaseHelper {
}
}
protected abstract InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference<Activity> activity,
final String sku, final String payload) throws UnsupportedOperationException;
@SuppressLint("StaticFieldLeak")
private class RequestInventoryTask extends AsyncTask<Void, Void, String> {
@ -808,6 +402,7 @@ public class InAppPurchaseHelper {
try {
JSONObject obj = new JSONObject(response);
JSONArray names = obj.names();
if (names != null) {
for (int i = 0; i < names.length(); i++) {
String skuType = names.getString(i);
JSONObject subObj = obj.getJSONObject(skuType);
@ -816,30 +411,32 @@ public class InAppPurchaseHelper {
getLiveUpdates().upgradeSubscription(sku);
}
}
}
} catch (JSONException e) {
logError("Json parsing error", e);
}
}
exec(InAppPurchaseTaskType.REQUEST_INVENTORY, new InAppRunnable() {
exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand());
}
}
protected abstract InAppCommand getRequestInventoryCommand() throws UnsupportedOperationException;
protected void onSkuDetailsResponseDone(List<PurchaseInfo> purchaseInfoList) {
final AndroidNetworkUtils.OnRequestResultListener listener = new AndroidNetworkUtils.OnRequestResultListener() {
@Override
public boolean run(InAppPurchaseHelper helper) {
logDebug("Setup successful. Querying inventory.");
try {
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
billingManager.queryPurchases();
} else {
throw new IllegalStateException("BillingManager disposed");
}
return false;
} catch (Exception e) {
logError("queryInventoryAsync Error", e);
public void onResult(String result) {
notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY);
notifyGetItems();
stop(true);
logDebug("Initial inapp query finished");
}
return true;
}
});
};
if (purchaseInfoList.size() > 0) {
sendTokens(purchaseInfoList, listener);
} else {
listener.onResult("OK");
}
}
@ -852,25 +449,16 @@ public class InAppPurchaseHelper {
parameters.put("aid", ctx.getUserAndroidId());
}
// Call when a purchase is finished
private void onPurchaseFinished(Purchase purchase) {
logDebug("Purchase finished: " + purchase);
// if we were disposed of in the meantime, quit.
if (getBillingManager() == null) {
stop(true);
return;
}
protected void onPurchaseDone(PurchaseInfo info) {
logDebug("Purchase successful.");
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku());
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(info.getSku());
if (liveUpdatesPurchase != null) {
// bought live updates
logDebug("Live updates subscription purchased.");
final String sku = liveUpdatesPurchase.getSku();
liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED);
sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() {
sendTokens(Collections.singletonList(info), new OnRequestResultListener() {
@Override
public void onResult(String result) {
boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get();
@ -887,7 +475,7 @@ public class InAppPurchaseHelper {
}
});
} else if (purchase.getSku().equals(getFullVersion().getSku())) {
} else if (info.getSku().equals(getFullVersion().getSku())) {
// bought full version
getFullVersion().setPurchaseState(PurchaseState.PURCHASED);
logDebug("Full version purchased.");
@ -898,7 +486,7 @@ public class InAppPurchaseHelper {
notifyItemPurchased(getFullVersion().getSku(), false);
stop(true);
} else if (purchase.getSku().equals(getDepthContours().getSku())) {
} else if (info.getSku().equals(getDepthContours().getSku())) {
// bought sea depth contours
getDepthContours().setPurchaseState(PurchaseState.PURCHASED);
logDebug("Sea depth contours purchased.");
@ -910,6 +498,17 @@ public class InAppPurchaseHelper {
notifyItemPurchased(getDepthContours().getSku(), false);
stop(true);
} else if (info.getSku().equals(getContourLines().getSku())) {
// bought contour lines
getContourLines().setPurchaseState(PurchaseState.PURCHASED);
logDebug("Contours lines purchased.");
showToast(ctx.getString(R.string.contour_lines_thanks));
ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES);
notifyItemPurchased(getContourLines().getSku(), false);
stop(true);
} else {
notifyDismissProgress(activeTask);
stop(true);
@ -921,17 +520,19 @@ public class InAppPurchaseHelper {
stop(false);
}
private void stop(boolean taskDone) {
protected abstract boolean isBillingManagerExists();
protected abstract void destroyBillingManager();
protected void stop(boolean taskDone) {
logDebug("Destroying helper.");
BillingManager billingManager = getBillingManager();
if (billingManager != null) {
if (isBillingManagerExists()) {
if (taskDone) {
processingTask = false;
}
if (!processingTask) {
activeTask = null;
billingManager.destroy();
this.billingManager = null;
destroyBillingManager();
}
} else {
processingTask = false;
@ -943,7 +544,7 @@ public class InAppPurchaseHelper {
}
}
private void sendTokens(@NonNull final List<Purchase> purchases, final OnRequestResultListener listener) {
protected void sendTokens(@NonNull final List<PurchaseInfo> purchaseInfoList, final OnRequestResultListener listener) {
final String userId = ctx.getSettings().BILLING_USER_ID.get();
final String token = ctx.getSettings().BILLING_USER_TOKEN.get();
final String email = ctx.getSettings().BILLING_USER_EMAIL.get();
@ -951,12 +552,12 @@ public class InAppPurchaseHelper {
String url = "https://osmand.net/subscription/purchased";
String userOperation = "Sending purchase info...";
final List<AndroidNetworkUtils.Request> requests = new ArrayList<>();
for (Purchase purchase : purchases) {
for (PurchaseInfo info : purchaseInfoList) {
Map<String, String> parameters = new HashMap<>();
parameters.put("userid", userId);
parameters.put("sku", purchase.getSku());
parameters.put("orderId", purchase.getOrderId());
parameters.put("purchaseToken", purchase.getPurchaseToken());
parameters.put("sku", info.getSku());
parameters.put("orderId", info.getOrderId());
parameters.put("purchaseToken", info.getPurchaseToken());
parameters.put("email", email);
parameters.put("token", token);
addUserInfo(parameters);
@ -967,9 +568,9 @@ public class InAppPurchaseHelper {
public void onResult(@NonNull List<RequestResponse> results) {
for (RequestResponse rr : results) {
String sku = rr.getRequest().getParameters().get("sku");
Purchase purchase = getPurchase(sku);
if (purchase != null) {
updateSentTokens(purchase);
PurchaseInfo info = getPurchaseInfo(sku);
if (info != null) {
updateSentTokens(info);
String result = rr.getResponse();
if (result != null) {
try {
@ -979,13 +580,13 @@ public class InAppPurchaseHelper {
} else {
complain("SendToken Error: "
+ obj.getString("error")
+ " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")");
+ " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")");
}
} catch (JSONException e) {
logError("SendToken", e);
complain("SendToken Error: "
+ (e.getMessage() != null ? e.getMessage() : "JSONException")
+ " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")");
+ " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")");
}
}
}
@ -995,10 +596,10 @@ public class InAppPurchaseHelper {
}
}
private void updateSentTokens(@NonNull Purchase purchase) {
private void updateSentTokens(@NonNull PurchaseInfo info) {
String tokensSentStr = ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get();
Set<String> tokensSent = new HashSet<>(Arrays.asList(tokensSentStr.split(";")));
tokensSent.add(purchase.getSku());
tokensSent.add(info.getSku());
ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.set(TextUtils.join(";", tokensSent));
}
@ -1032,10 +633,10 @@ public class InAppPurchaseHelper {
}
@Nullable
private Purchase getPurchase(String sku) {
for (Purchase purchase : purchases) {
if (purchase.getSku().equals(sku)) {
return purchase;
private PurchaseInfo getPurchaseInfo(String sku) {
for (PurchaseInfo info : purchaseInfoList) {
if (info.getSku().equals(sku)) {
return info;
}
}
return null;
@ -1049,31 +650,35 @@ public class InAppPurchaseHelper {
}
}
private void notifyError(InAppPurchaseTaskType taskType, String message) {
public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, Intent data) {
return false;
}
protected void notifyError(InAppPurchaseTaskType taskType, String message) {
if (uiActivity != null) {
uiActivity.onError(taskType, message);
}
}
private void notifyGetItems() {
protected void notifyGetItems() {
if (uiActivity != null) {
uiActivity.onGetItems();
}
}
private void notifyItemPurchased(String sku, boolean active) {
protected void notifyItemPurchased(String sku, boolean active) {
if (uiActivity != null) {
uiActivity.onItemPurchased(sku, active);
}
}
private void notifyShowProgress(InAppPurchaseTaskType taskType) {
protected void notifyShowProgress(InAppPurchaseTaskType taskType) {
if (uiActivity != null) {
uiActivity.showProgress(taskType);
}
}
private void notifyDismissProgress(InAppPurchaseTaskType taskType) {
protected void notifyDismissProgress(InAppPurchaseTaskType taskType) {
if (uiActivity != null) {
uiActivity.dismissProgress(taskType);
}
@ -1090,26 +695,26 @@ public class InAppPurchaseHelper {
}
}
private void complain(String message) {
protected void complain(String message) {
logError("**** InAppPurchaseHelper Error: " + message);
showToast(message);
}
private void showToast(final String message) {
protected void showToast(final String message) {
ctx.showToastMessage(message);
}
private void logDebug(String msg) {
protected void logDebug(String msg) {
if (mDebugLog) {
Log.d(TAG, msg);
}
}
private void logError(String msg) {
protected void logError(String msg) {
Log.e(TAG, msg);
}
private void logError(String msg, Throwable e) {
protected void logError(String msg, Throwable e) {
Log.e(TAG, "Error: " + msg, e);
}

View file

@ -11,14 +11,11 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.SkuDetails;
import net.osmand.AndroidUtils;
import net.osmand.Period;
import net.osmand.Period.PeriodUnit;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.util.Algorithms;
@ -33,64 +30,17 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class InAppPurchases {
public abstract class InAppPurchases {
private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion();
private static final InAppPurchaseDepthContoursFull DEPTH_CONTOURS_FULL = new InAppPurchaseDepthContoursFull();
private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree();
private static final InAppPurchaseContourLinesFull CONTOUR_LINES_FULL = new InAppPurchaseContourLinesFull();
private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree();
protected InAppPurchase fullVersion;
protected InAppPurchase depthContours;
protected InAppPurchase contourLines;
protected InAppSubscription monthlyLiveUpdates;
protected InAppSubscription discountedMonthlyLiveUpdates;
protected InAppSubscriptionList liveUpdates;
protected InAppPurchase[] inAppPurchases;
private static final InAppSubscription[] LIVE_UPDATES_FULL = new InAppSubscription[]{
new InAppPurchaseLiveUpdatesOldMonthlyFull(),
new InAppPurchaseLiveUpdatesMonthlyFull(),
new InAppPurchaseLiveUpdates3MonthsFull(),
new InAppPurchaseLiveUpdatesAnnualFull()
};
private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{
new InAppPurchaseLiveUpdatesOldMonthlyFree(),
new InAppPurchaseLiveUpdatesMonthlyFree(),
new InAppPurchaseLiveUpdates3MonthsFree(),
new InAppPurchaseLiveUpdatesAnnualFree()
};
private InAppPurchase fullVersion;
private InAppPurchase depthContours;
private InAppPurchase contourLines;
private InAppSubscription monthlyLiveUpdates;
private InAppSubscription discountedMonthlyLiveUpdates;
private InAppSubscriptionList liveUpdates;
private InAppPurchase[] inAppPurchases;
InAppPurchases(OsmandApplication ctx) {
fullVersion = FULL_VERSION;
if (Version.isFreeVersion(ctx)) {
liveUpdates = new LiveUpdatesInAppPurchasesFree();
} else {
liveUpdates = new LiveUpdatesInAppPurchasesFull();
}
for (InAppSubscription s : liveUpdates.getAllSubscriptions()) {
if (s instanceof InAppPurchaseLiveUpdatesMonthly) {
if (s.isDiscounted()) {
discountedMonthlyLiveUpdates = s;
} else {
monthlyLiveUpdates = s;
}
}
}
if (Version.isFreeVersion(ctx)) {
depthContours = DEPTH_CONTOURS_FREE;
} else {
depthContours = DEPTH_CONTOURS_FULL;
}
if (Version.isFreeVersion(ctx)) {
contourLines = CONTOUR_LINES_FREE;
} else {
contourLines = CONTOUR_LINES_FULL;
}
inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines };
protected InAppPurchases(OsmandApplication ctx) {
}
public InAppPurchase getFullVersion() {
@ -123,7 +73,7 @@ public class InAppPurchases {
public InAppSubscription getPurchasedMonthlyLiveUpdates() {
if (monthlyLiveUpdates.isAnyPurchased()) {
return monthlyLiveUpdates;
} else if (discountedMonthlyLiveUpdates.isAnyPurchased()) {
} else if (discountedMonthlyLiveUpdates != null && discountedMonthlyLiveUpdates.isAnyPurchased()) {
return discountedMonthlyLiveUpdates;
}
return null;
@ -158,31 +108,13 @@ public class InAppPurchases {
return null;
}
public boolean isFullVersion(String sku) {
return FULL_VERSION.getSku().equals(sku);
}
public abstract boolean isFullVersion(String sku);
public boolean isDepthContours(String sku) {
return DEPTH_CONTOURS_FULL.getSku().equals(sku) || DEPTH_CONTOURS_FREE.getSku().equals(sku);
}
public abstract boolean isDepthContours(String sku);
public boolean isContourLines(String sku) {
return CONTOUR_LINES_FULL.getSku().equals(sku) || CONTOUR_LINES_FREE.getSku().equals(sku);
}
public abstract boolean isContourLines(String sku);
public boolean isLiveUpdates(String sku) {
for (InAppPurchase p : LIVE_UPDATES_FULL) {
if (p.getSku().equals(sku)) {
return true;
}
}
for (InAppPurchase p : LIVE_UPDATES_FREE) {
if (p.getSku().equals(sku)) {
return true;
}
}
return false;
}
public abstract boolean isLiveUpdates(String sku);
public abstract static class InAppSubscriptionList {
@ -260,20 +192,6 @@ public class InAppPurchases {
}
}
public static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList {
public LiveUpdatesInAppPurchasesFree() {
super(LIVE_UPDATES_FREE);
}
}
public static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList {
public LiveUpdatesInAppPurchasesFull() {
super(LIVE_UPDATES_FULL);
}
}
public abstract static class InAppPurchase {
public enum PurchaseState {
@ -295,11 +213,11 @@ public class InAppPurchases {
private NumberFormat currencyFormatter;
private InAppPurchase(@NonNull String sku) {
protected InAppPurchase(@NonNull String sku) {
this.sku = sku;
}
private InAppPurchase(@NonNull String sku, boolean discounted) {
protected InAppPurchase(@NonNull String sku, boolean discounted) {
this(sku);
this.discounted = discounted;
}
@ -777,23 +695,9 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseFullVersion extends InAppPurchase {
private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price";
InAppPurchaseFullVersion() {
super(SKU_FULL_VERSION_PRICE);
}
@Override
public String getDefaultPrice(Context ctx) {
return ctx.getString(R.string.full_version_price);
}
}
public static class InAppPurchaseDepthContours extends InAppPurchase {
private InAppPurchaseDepthContours(String sku) {
protected InAppPurchaseDepthContours(String sku) {
super(sku);
}
@ -803,27 +707,9 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseDepthContoursFull extends InAppPurchaseDepthContours {
private static final String SKU_DEPTH_CONTOURS_FULL = "net.osmand.seadepth_plus";
InAppPurchaseDepthContoursFull() {
super(SKU_DEPTH_CONTOURS_FULL);
}
}
public static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours {
private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth";
InAppPurchaseDepthContoursFree() {
super(SKU_DEPTH_CONTOURS_FREE);
}
}
public static class InAppPurchaseContourLines extends InAppPurchase {
private InAppPurchaseContourLines(String sku) {
protected InAppPurchaseContourLines(String sku) {
super(sku);
}
@ -833,25 +719,7 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseContourLinesFull extends InAppPurchaseContourLines {
private static final String SKU_CONTOUR_LINES_FULL = "net.osmand.contourlines_plus";
InAppPurchaseContourLinesFull() {
super(SKU_CONTOUR_LINES_FULL);
}
}
public static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines {
private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines";
InAppPurchaseContourLinesFree() {
super(SKU_CONTOUR_LINES_FREE);
}
}
public static abstract class InAppPurchaseLiveUpdatesMonthly extends InAppSubscription {
protected static abstract class InAppPurchaseLiveUpdatesMonthly extends InAppSubscription {
InAppPurchaseLiveUpdatesMonthly(String skuNoVersion, int version) {
super(skuNoVersion, version);
@ -905,45 +773,7 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseLiveUpdatesMonthlyFull extends InAppPurchaseLiveUpdatesMonthly {
private static final String SKU_LIVE_UPDATES_MONTHLY_FULL = "osm_live_subscription_monthly_full";
InAppPurchaseLiveUpdatesMonthlyFull() {
super(SKU_LIVE_UPDATES_MONTHLY_FULL, 1);
}
private InAppPurchaseLiveUpdatesMonthlyFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFull(sku) : null;
}
}
public static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly {
private static final String SKU_LIVE_UPDATES_MONTHLY_FREE = "osm_live_subscription_monthly_free";
InAppPurchaseLiveUpdatesMonthlyFree() {
super(SKU_LIVE_UPDATES_MONTHLY_FREE, 1);
}
private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null;
}
}
public static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription {
protected static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription {
InAppPurchaseLiveUpdates3Months(String skuNoVersion, int version) {
super(skuNoVersion, version);
@ -986,45 +816,7 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseLiveUpdates3MonthsFull extends InAppPurchaseLiveUpdates3Months {
private static final String SKU_LIVE_UPDATES_3_MONTHS_FULL = "osm_live_subscription_3_months_full";
InAppPurchaseLiveUpdates3MonthsFull() {
super(SKU_LIVE_UPDATES_3_MONTHS_FULL, 1);
}
private InAppPurchaseLiveUpdates3MonthsFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFull(sku) : null;
}
}
public static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months {
private static final String SKU_LIVE_UPDATES_3_MONTHS_FREE = "osm_live_subscription_3_months_free";
InAppPurchaseLiveUpdates3MonthsFree() {
super(SKU_LIVE_UPDATES_3_MONTHS_FREE, 1);
}
private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null;
}
}
public static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription {
protected static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription {
InAppPurchaseLiveUpdatesAnnual(String skuNoVersion, int version) {
super(skuNoVersion, version);
@ -1067,44 +859,6 @@ public class InAppPurchases {
}
}
public static class InAppPurchaseLiveUpdatesAnnualFull extends InAppPurchaseLiveUpdatesAnnual {
private static final String SKU_LIVE_UPDATES_ANNUAL_FULL = "osm_live_subscription_annual_full";
InAppPurchaseLiveUpdatesAnnualFull() {
super(SKU_LIVE_UPDATES_ANNUAL_FULL, 1);
}
private InAppPurchaseLiveUpdatesAnnualFull(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFull(sku) : null;
}
}
public static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual {
private static final String SKU_LIVE_UPDATES_ANNUAL_FREE = "osm_live_subscription_annual_free";
InAppPurchaseLiveUpdatesAnnualFree() {
super(SKU_LIVE_UPDATES_ANNUAL_FREE, 1);
}
private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) {
super(sku);
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null;
}
}
public static class InAppPurchaseLiveUpdatesOldMonthly extends InAppPurchaseLiveUpdatesMonthly {
InAppPurchaseLiveUpdatesOldMonthly(String sku) {
@ -1127,54 +881,5 @@ public class InAppPurchases {
return null;
}
}
public static class InAppPurchaseLiveUpdatesOldMonthlyFull extends InAppPurchaseLiveUpdatesOldMonthly {
private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FULL = "osm_live_subscription_2";
InAppPurchaseLiveUpdatesOldMonthlyFull() {
super(SKU_LIVE_UPDATES_OLD_MONTHLY_FULL);
}
}
public static class InAppPurchaseLiveUpdatesOldMonthlyFree extends InAppPurchaseLiveUpdatesOldMonthly {
private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FREE = "osm_free_live_subscription_2";
InAppPurchaseLiveUpdatesOldMonthlyFree() {
super(SKU_LIVE_UPDATES_OLD_MONTHLY_FREE);
}
}
public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription {
private SkuDetails details;
InAppPurchaseLiveUpdatesOldSubscription(@NonNull SkuDetails details) {
super(details.getSku(), true);
this.details = details;
}
@Override
public String getDefaultPrice(Context ctx) {
return "";
}
@Override
public CharSequence getTitle(Context ctx) {
return details.getTitle();
}
@Override
public CharSequence getDescription(@NonNull Context ctx) {
return details.getDescription();
}
@Nullable
@Override
protected InAppSubscription newInstance(@NonNull String sku) {
return null;
}
}
}

View file

@ -2008,6 +2008,7 @@ public class OsmandSettings {
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference("live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal();
public final OsmandPreference<Boolean> FULL_VERSION_PURCHASED = new BooleanPreference("billing_full_version_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> DEPTH_CONTOURS_PURCHASED = new BooleanPreference("billing_sea_depth_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> CONTOUR_LINES_PURCHASED = new BooleanPreference("billing_srtm_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> EMAIL_SUBSCRIBED = new BooleanPreference("email_subscribed", false).makeGlobal();
public final OsmandPreference<Integer> DISCOUNT_ID = new IntPreference("discount_id", 0).makeGlobal();

View file

@ -95,7 +95,9 @@ public class SRTMPlugin extends OsmandPlugin {
@Override
protected boolean pluginAvailable(OsmandApplication app) {
return super.pluginAvailable(app) || InAppPurchaseHelper.isSubscribedToLiveUpdates(app);
return super.pluginAvailable(app)
|| InAppPurchaseHelper.isSubscribedToLiveUpdates(app)
|| InAppPurchaseHelper.isContourLinesPurchased(app);
}
@Override
@ -359,7 +361,7 @@ public class SRTMPlugin extends OsmandPlugin {
.setTitleId(R.string.shared_string_terrain, mapActivity)
.setDescription(app.getString(terrainMode == TerrainMode.HILLSHADE
? R.string.shared_string_hillshade
: R.string.shared_string_slope))
: R.string.download_slope_maps))
.setSelected(terrainEnabled)
.setColor(terrainEnabled ? R.color.osmand_orange : ContextMenuItem.INVALID_ID)
.setIcon(R.drawable.ic_action_hillshade_dark)

View file

@ -33,20 +33,26 @@ public class GeometryWayDrawer<T extends GeometryWayContext> {
int w = tb.getPixWidth();
int left = -w / 4;
int right = w + w / 4;
int top = - h/4;
int bottom = h + h/4;
int top = -h / 4;
int bottom = h + h / 4;
boolean hasStyles = styles != null && styles.size() == tx.size();
double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f;
int startIndex = tx.size() - 2;
double defaultPxStep;
if (hasStyles && styles.get(startIndex) != null) {
defaultPxStep = styles.get(startIndex).getPointStepPx(zoomCoef);
} else {
Bitmap arrow = context.getArrowBitmap();
int arrowHeight = arrow.getHeight();
double pxStep = arrowHeight * 4f * zoomCoef;
double pxStepRegular = arrowHeight * 4f * zoomCoef;
defaultPxStep = arrow.getHeight() * 4f * zoomCoef;
}
double pxStep = defaultPxStep;
double dist = 0;
if (distPixToFinish != 0) {
dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1
}
for (int i = tx.size() - 2; i >= 0; i --) {
for (int i = startIndex; i >= 0; i--) {
GeometryWayStyle<?> style = hasStyles ? styles.get(i) : null;
float px = tx.get(i);
float py = ty.get(i);
@ -57,7 +63,7 @@ public class GeometryWayDrawer<T extends GeometryWayContext> {
if (distSegment == 0) {
continue;
}
pxStep = style != null ? style.getPointStepPx(zoomCoef) : pxStepRegular;
pxStep = style != null ? style.getPointStepPx(zoomCoef) : defaultPxStep;
if (dist >= pxStep) {
dist = 0;
}

View file

@ -4,6 +4,9 @@ buildscript {
google()
mavenCentral()
jcenter()
maven {
url 'https://developer.huawei.com/repo/'
}
}
dependencies {
//classpath 'com.android.tools.build:gradle:2.+'
@ -11,6 +14,9 @@ buildscript {
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
if (gradle.startParameter.taskNames.toString().contains("huawei")) {
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
}
}
}
@ -32,5 +38,8 @@ allprojects {
maven {
url "https://jitpack.io"
}
maven {
url 'https://developer.huawei.com/repo/'
}
}
}