Merge pull request #9945 from osmandapp/master

update test branch
This commit is contained in:
Hardy 2020-10-04 22:54:05 +02:00 committed by GitHub
commit e2ff68ac0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 3355 additions and 1258 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

@ -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"
}
@ -185,21 +196,16 @@ android {
dimension "version"
applicationId "net.osmand.plus"
}
fulldev {
dimension "version"
applicationId "net.osmand.plus"
resConfig "en"
//resConfigs "xxhdpi", "nodpi"
}
huawei {
fulldev {
dimension "version"
applicationId "net.osmand.plus.huawei"
applicationId "net.osmand.plus"
resConfig "en"
// resConfigs "xxhdpi", "nodpi"
}
freehuawei {
dimension "version"
applicationId "net.osmand.huawei"
}
// CoreVersion
legacy {
dimension "coreversion"
@ -222,7 +228,11 @@ android {
buildConfigField "boolean", "USE_DEBUG_LIBRARIES", "false"
}
release {
signingConfig signingConfigs.publishing
if (gradle.startParameter.taskNames.toString().contains("huawei")) {
signingConfig signingConfigs.publishingHuawei
} else {
signingConfig signingConfigs.publishing
}
}
}
@ -279,46 +289,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"
@ -400,8 +378,6 @@ task copyLargePOIIcons(type: Sync) {
}
}
task copyWidgetIconsXhdpi(type: Sync) {
from "res/drawable-xxhdpi/"
into "res/drawable-large-xhdpi/"
@ -448,13 +424,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
}
}
@ -506,10 +478,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) {
@ -519,7 +497,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')
@ -568,6 +545,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

@ -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

@ -2006,14 +2006,14 @@
<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="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>
@ -2029,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>
@ -2044,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>
@ -2417,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>
@ -2492,7 +2492,7 @@
<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 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>
@ -2601,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>
@ -2954,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>
@ -3847,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

@ -3903,4 +3903,5 @@
<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

@ -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

@ -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

@ -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

@ -3906,4 +3906,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/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>
@ -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>
@ -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>
@ -521,11 +521,11 @@
<string name="switch_to_vector_map_to_see">Для этого региона есть локальные векторные карты.
\n\t
\n\tДля использования выберите их в качестве источника (Меню → Настройка карты → Источник карты → Локальные векторные карты).</string>
<string name="choose_audio_stream">Голосовые инструкции</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,7 +1201,7 @@
<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>
@ -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>
@ -1272,10 +1272,10 @@
<string name="local_osm_changes_backup">Резервное копирование как правка OSM</string>
<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>
<string name="use_distance_measurement_help">* Нажмите, чтобы отметить точку.
\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>
@ -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>
@ -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>
@ -2520,14 +2520,14 @@
\n• Поддержка промежуточных точек маршрута
\n• Запись собственного или отправка GPX трека и следование ему
\n</string>
<string name="osmand_extended_description_part3">Карта
\n• Отображает POI (точки интереса) около вас
\n• Адаптирует карту в направлении вашего движения (или компаса)
\n• Показывает, где вы находитесь и куда вы смотрите
\n• Делитесь своим расположением, чтобы друзья смогли найти вас
\n• Сохраняет ваши самые важные места в избранных
\n• Позволяет вам выбрать как отображать названия на карте: на английском, местным или с фонетическим написанием
\n• Отображает специальные онлайн-тайлы, спутниковые снимки (с Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью
<string name="osmand_extended_description_part3">Карта
\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>
@ -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>
@ -3555,7 +3555,7 @@
<string name="search_offline_geo_error">Невозможно разобрать геоссылку «%s».</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_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="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>
@ -3885,7 +3885,7 @@
<string name="shared_string_redo">ПОВТОРИТЬ</string>
<string name="release_3_8">• Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам
\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

@ -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

@ -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);

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,17 +49,39 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
protected void onDestroy() {
super.onDestroy();
deinitInAppPurchaseHelper();
activityDestroyed = true;
}
private void initInAppPurchaseHelper() {
deinitInAppPurchaseHelper();
if (purchaseHelper != null) {
purchaseHelper.setUiActivity(this);
if (purchaseHelper.needRequestInventory()) {
purchaseHelper.requestInventory();
if (purchaseHelper == null) {
OsmandApplication app = getMyApplication();
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (app.getSettings().isInternetConnectionAvailable()
&& isInAppPurchaseAllowed()
&& isInAppPurchaseSupported(purchaseHelper)) {
this.purchaseHelper = purchaseHelper;
}
}
if (purchaseHelper != null) {
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() {
@ -80,7 +98,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
if (purchaseHelper != null) {
app.logEvent("in_app_purchase_redirect");
purchaseHelper.purchaseFullVersion(activity);
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");
purchaseHelper.purchaseDepthContours(activity);
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

@ -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");
purchaseHelper.purchaseFullVersion(mapActivity);
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())) {

File diff suppressed because it is too large Load diff

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

@ -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/'
}
}
}