Merge branch 'master' into Redesign-voice

This commit is contained in:
Vitaliy 2021-04-21 14:02:14 +03:00
commit 2ee1b26289
32 changed files with 2027 additions and 648 deletions

View file

@ -42,6 +42,7 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<ScrollView <ScrollView
android:id="@+id/main_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -121,6 +122,13 @@
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size" /> android:textSize="@dimen/default_list_text_size" />
<include
android:id="@+id/btn_refresh"
layout="@layout/bottom_sheet_dialog_button"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height"
android:layout_marginTop="@dimen/content_padding_half" />
<include <include
android:id="@+id/btn_backup" android:id="@+id/btn_backup"
layout="@layout/bottom_sheet_dialog_button" layout="@layout/bottom_sheet_dialog_button"

View file

@ -3837,4 +3837,8 @@
<string name="track_has_no_altitude">Sporet indeholder ikke højdedata.</string> <string name="track_has_no_altitude">Sporet indeholder ikke højdedata.</string>
<string name="track_has_no_speed">Sporet indeholder ikke hastighedsdata.</string> <string name="track_has_no_speed">Sporet indeholder ikke hastighedsdata.</string>
<string name="select_another_colorization">Vælg en anden type farvelægning.</string> <string name="select_another_colorization">Vælg en anden type farvelægning.</string>
<string name="exit_number">Udgangsnummer</string>
<string name="announce_when_exceeded">Meddelelse ved overskridelse</string>
<string name="user_points">Bruger points</string>
<string name="output">Resultat</string>
</resources> </resources>

View file

@ -1460,4 +1460,5 @@
<string name="poi_shoes_women">بانوان</string> <string name="poi_shoes_women">بانوان</string>
<string name="poi_clothes_women">بانوان</string> <string name="poi_clothes_women">بانوان</string>
<string name="poi_clothes_workwear">لباس کار</string> <string name="poi_clothes_workwear">لباس کار</string>
<string name="poi_greenhouse_horticulture">باغداری گلخانه‌ای</string>
</resources> </resources>

View file

@ -411,10 +411,10 @@
<string name="poi_water_tower">Reservatório elevado</string> <string name="poi_water_tower">Reservatório elevado</string>
<string name="poi_lock_gate">Comporta de eclusa</string> <string name="poi_lock_gate">Comporta de eclusa</string>
<string name="poi_waterway_turning_point">Ponto de viragem fluvial</string> <string name="poi_waterway_turning_point">Ponto de viragem fluvial</string>
<string name="poi_weir">Represa;Açude</string> <string name="poi_weir">Represa/açude;Represa;Açude</string>
<string name="poi_dam">Barragem</string> <string name="poi_dam">Barragem</string>
<string name="poi_watermill">Moinho de água</string> <string name="poi_watermill">Moinho de água</string>
<string name="poi_breakwater">Quebra-mar;Molhe</string> <string name="poi_breakwater">Quebra-mar/molhe;Quebra-mar;Molhe</string>
<string name="poi_groyne">Espigão marítimo</string> <string name="poi_groyne">Espigão marítimo</string>
<string name="poi_power_substation">Subestação</string> <string name="poi_power_substation">Subestação</string>
<string name="poi_power_transformer">Transformador</string> <string name="poi_power_transformer">Transformador</string>
@ -565,14 +565,14 @@
<string name="poi_capital">Sim</string> <string name="poi_capital">Sim</string>
<string name="poi_town">Sede de concelho</string> <string name="poi_town">Sede de concelho</string>
<string name="poi_village">Sede de freguesia</string> <string name="poi_village">Sede de freguesia</string>
<string name="poi_hamlet">Aldeia;Lugar</string> <string name="poi_hamlet">Aldeia/lugar;Aldeia;Lugar</string>
<string name="poi_isolated_dwelling">Moradia isolada</string> <string name="poi_isolated_dwelling">Moradia isolada</string>
<string name="poi_suburb">Subúrbio</string> <string name="poi_suburb">Subúrbio</string>
<string name="poi_quarter">Zona de cidade</string> <string name="poi_quarter">Zona de cidade</string>
<string name="poi_neighbourhood">Bairro</string> <string name="poi_neighbourhood">Bairro</string>
<string name="poi_locality">Localidade</string> <string name="poi_locality">Localidade</string>
<string name="poi_place_allotments">Horta comunitária</string> <string name="poi_place_allotments">Horta comunitária</string>
<string name="poi_place_farm">Quinta;Fazenda</string> <string name="poi_place_farm">Quinta/fazenda;Quinta;Fazenda</string>
<string name="poi_pharmacy">Farmácia</string> <string name="poi_pharmacy">Farmácia</string>
<string name="poi_hospital">Hospital</string> <string name="poi_hospital">Hospital</string>
<string name="poi_doctors">Consultório médico</string> <string name="poi_doctors">Consultório médico</string>
@ -694,7 +694,7 @@
<string name="poi_boundary_stone">Marco de fronteira</string> <string name="poi_boundary_stone">Marco de fronteira</string>
<string name="poi_historic_cannon">Canhão histórico</string> <string name="poi_historic_cannon">Canhão histórico</string>
<string name="poi_castle">Castelo</string> <string name="poi_castle">Castelo</string>
<string name="poi_city_gate">Portão da cidade</string> <string name="poi_city_gate">Portão/arco de cidade</string>
<string name="poi_fort">Forte</string> <string name="poi_fort">Forte</string>
<string name="poi_fountain">Chafariz</string> <string name="poi_fountain">Chafariz</string>
<string name="poi_historic_ruins">Ruínas históricas</string> <string name="poi_historic_ruins">Ruínas históricas</string>
@ -721,7 +721,7 @@
<string name="poi_attraction_water_slide">Tobogã aquático</string> <string name="poi_attraction_water_slide">Tobogã aquático</string>
<string name="poi_lodging">Alojamento</string> <string name="poi_lodging">Alojamento</string>
<string name="poi_hotel">Hotel</string> <string name="poi_hotel">Hotel</string>
<string name="poi_guest_house">Pensão;Albergaria;Hospedaria;Estalagem;Residencial</string> <string name="poi_guest_house">Pensão/albergaria/residenial;Estalagem;Residencial;Albergaria;Hospedaria;Casa de hóspedes</string>
<string name="poi_hostel">Hostel</string> <string name="poi_hostel">Hostel</string>
<string name="poi_motel">Hotel estrada</string> <string name="poi_motel">Hotel estrada</string>
<string name="poi_alpine_hut">Abrigo de montanha</string> <string name="poi_alpine_hut">Abrigo de montanha</string>
@ -890,7 +890,7 @@
<string name="poi_theatre_genre_circus">Circo</string> <string name="poi_theatre_genre_circus">Circo</string>
<string name="poi_gallery">Galeria de arte</string> <string name="poi_gallery">Galeria de arte</string>
<string name="poi_dance_floor">Pista de dança</string> <string name="poi_dance_floor">Pista de dança</string>
<string name="poi_nightclub">Discoteca;Danceteria</string> <string name="poi_nightclub">Discoteca/danceteria;Discoteca;Danceteria</string>
<string name="poi_stripclub">Clube de striptease</string> <string name="poi_stripclub">Clube de striptease</string>
<string name="poi_ski_resort">Resort de esqui</string> <string name="poi_ski_resort">Resort de esqui</string>
<string name="poi_beach_resort">Resort de praia</string> <string name="poi_beach_resort">Resort de praia</string>
@ -912,9 +912,9 @@
<string name="poi_restaurant">Restaurante</string> <string name="poi_restaurant">Restaurante</string>
<string name="poi_fast_food">Comida rápida</string> <string name="poi_fast_food">Comida rápida</string>
<string name="poi_bar">Bar</string> <string name="poi_bar">Bar</string>
<string name="poi_pub">Taberna</string> <string name="poi_pub">Taberna/pub/tasca;Taberna;Tasco;Tasca;Pub;Boteco;Buteco;Botequim</string>
<string name="poi_food_court">Praça de alimentação</string> <string name="poi_food_court">Praça de alimentação</string>
<string name="poi_drinking_water">Bebedouro (água potável para beber)</string> <string name="poi_drinking_water">Bebedouro (água potável)</string>
<string name="poi_barbecue">Churrasqueira</string> <string name="poi_barbecue">Churrasqueira</string>
<string name="poi_craft_agricultural_engines">Máquinas agrícolas</string> <string name="poi_craft_agricultural_engines">Máquinas agrícolas</string>
<string name="poi_craft_basket_maker">Cesteiro</string> <string name="poi_craft_basket_maker">Cesteiro</string>
@ -977,8 +977,8 @@
<string name="poi_car_pooling">Ponto de boleia solidária de carro</string> <string name="poi_car_pooling">Ponto de boleia solidária de carro</string>
<string name="poi_boat_sharing">Ponto de barcos partilhados</string> <string name="poi_boat_sharing">Ponto de barcos partilhados</string>
<string name="poi_dock">Doca</string> <string name="poi_dock">Doca</string>
<string name="poi_cutline">Linha de corte florestal;Atalhada;Linha corta-fogo</string> <string name="poi_cutline">Linha de corte florestal/atalhada;Atalhada;Linha corta-fogo;Linha de corte florestal</string>
<string name="poi_toilets">Casa de banho;Banheiros</string> <string name="poi_toilets">Casa de banho;Banheiros;WC</string>
<string name="poi_shower">Chuveiros públicos</string> <string name="poi_shower">Chuveiros públicos</string>
<string name="poi_sauna">Sauna</string> <string name="poi_sauna">Sauna</string>
<string name="poi_brothel">Bordel</string> <string name="poi_brothel">Bordel</string>
@ -1002,7 +1002,7 @@
<string name="poi_ridge">Cumeeira</string> <string name="poi_ridge">Cumeeira</string>
<string name="poi_glacier">Glaciar</string> <string name="poi_glacier">Glaciar</string>
<string name="poi_sinkhole">Sumidouro</string> <string name="poi_sinkhole">Sumidouro</string>
<string name="poi_waterfall">Queda de água;Cascata;Salto;Catarata</string> <string name="poi_waterfall">Queda de água/cascata/catarata/salto;Cascata;Salto;Catarata;Queda de água</string>
<string name="poi_river">Rio</string> <string name="poi_river">Rio</string>
<string name="poi_stream">Ribeiro(a)</string> <string name="poi_stream">Ribeiro(a)</string>
<string name="poi_rapids">Rápidos</string> <string name="poi_rapids">Rápidos</string>
@ -1246,8 +1246,8 @@
<string name="poi_fee_no">Não</string> <string name="poi_fee_no">Não</string>
<string name="poi_drinking_water_yes">Sim</string> <string name="poi_drinking_water_yes">Sim</string>
<string name="poi_drinking_water_no">Não</string> <string name="poi_drinking_water_no">Não</string>
<string name="poi_supervised_yes">Vigiado</string> <string name="poi_supervised_yes">Vigiado: sim</string>
<string name="poi_supervised_no">Não vigiado</string> <string name="poi_supervised_no">Vigiado: não</string>
<string name="poi_seasonal_yes">Sim</string> <string name="poi_seasonal_yes">Sim</string>
<string name="poi_seasonal_no">Não</string> <string name="poi_seasonal_no">Não</string>
<string name="poi_seasonal_dry_season">Estação seca</string> <string name="poi_seasonal_dry_season">Estação seca</string>
@ -1433,7 +1433,7 @@
<string name="poi_horse_riding">Centro equestre</string> <string name="poi_horse_riding">Centro equestre</string>
<string name="poi_leisure_common">Área de lazer comum</string> <string name="poi_leisure_common">Área de lazer comum</string>
<string name="poi_garden">Jardim</string> <string name="poi_garden">Jardim</string>
<string name="poi_heath">Charneca;Mato de vegetação rasteira</string> <string name="poi_heath">Charneca/mato de vegetação rasteira;Charneca;Mato de vegetação rasteira</string>
<string name="poi_grass">Relvado</string> <string name="poi_grass">Relvado</string>
<string name="poi_grassland">Pradaria</string> <string name="poi_grassland">Pradaria</string>
<string name="poi_scrub">Matagal</string> <string name="poi_scrub">Matagal</string>
@ -1771,7 +1771,7 @@
<string name="poi_vending_toys">Brinquedos</string> <string name="poi_vending_toys">Brinquedos</string>
<string name="poi_vending_ice_cream">Gelados</string> <string name="poi_vending_ice_cream">Gelados</string>
<string name="poi_vending_sim_cards">Cartões de telemóvel (SIM)</string> <string name="poi_vending_sim_cards">Cartões de telemóvel (SIM)</string>
<string name="poi_branch">Filial;Sucursal</string> <string name="poi_branch">Filial/sucursal;Filial;Sucursal</string>
<string name="poi_memorial_war">Memorial de guerra</string> <string name="poi_memorial_war">Memorial de guerra</string>
<string name="poi_memorial_plaque">Placa comemorativa</string> <string name="poi_memorial_plaque">Placa comemorativa</string>
<string name="poi_memorial_statue">Estátua</string> <string name="poi_memorial_statue">Estátua</string>
@ -2242,7 +2242,7 @@
<string name="poi_step_condition_even">Condição dos degraus: regular</string> <string name="poi_step_condition_even">Condição dos degraus: regular</string>
<string name="poi_step_condition_uneven">Condição dos degraus: irregular</string> <string name="poi_step_condition_uneven">Condição dos degraus: irregular</string>
<string name="poi_step_condition_rough">Condição dos degraus: acidentada</string> <string name="poi_step_condition_rough">Condição dos degraus: acidentada</string>
<string name="poi_cairn">Moledro;Moledo;Melédro;Mariola</string> <string name="poi_cairn">Moledro/mariola;Moledo;Melédro;Mariola;Moledro</string>
<string name="poi_defibrillator">Desfibrilhador</string> <string name="poi_defibrillator">Desfibrilhador</string>
<string name="poi_defibrillator_yes">Desfibrilhador: sim</string> <string name="poi_defibrillator_yes">Desfibrilhador: sim</string>
<string name="poi_tomb_war_grave">Tipo: túmulo de guerra</string> <string name="poi_tomb_war_grave">Tipo: túmulo de guerra</string>
@ -2360,7 +2360,7 @@
<string name="poi_fortification_type_sconce">Tipo de fortificação: arandela</string> <string name="poi_fortification_type_sconce">Tipo de fortificação: arandela</string>
<string name="poi_fortification_type_ring_ditch">Tipo de fortificação: vala circular</string> <string name="poi_fortification_type_ring_ditch">Tipo de fortificação: vala circular</string>
<string name="poi_pa">Pa (assentamento fortificado maori)</string> <string name="poi_pa">Pa (assentamento fortificado maori)</string>
<string name="poi_historic_farm">Quinta histórica;Fazenda histórica</string> <string name="poi_historic_farm">Quinta/fazenda histórica;Quinta histórica;Fazenda histórica</string>
<string name="poi_historic_railway_station">Estação ferroviária histórica</string> <string name="poi_historic_railway_station">Estação ferroviária histórica</string>
<string name="poi_historic_threshing_floor">Eira histórica</string> <string name="poi_historic_threshing_floor">Eira histórica</string>
<string name="poi_historic_gallows">Forca histórica</string> <string name="poi_historic_gallows">Forca histórica</string>
@ -2476,7 +2476,7 @@
<string name="poi_health_specialty_behavior_yes">Comportamental</string> <string name="poi_health_specialty_behavior_yes">Comportamental</string>
<string name="poi_health_specialty_palliative_medicine_yes">Medicina paliativa</string> <string name="poi_health_specialty_palliative_medicine_yes">Medicina paliativa</string>
<string name="poi_building_type_pyramid">Tipo de edifício: pirâmide</string> <string name="poi_building_type_pyramid">Tipo de edifício: pirâmide</string>
<string name="poi_fitness_centre">Ginásio;Academia desportiva</string> <string name="poi_fitness_centre">Ginásio/academia desportiva;Ginásio;Academia desportiva</string>
<string name="poi_fitness">Exercício físico</string> <string name="poi_fitness">Exercício físico</string>
<string name="poi_billiards">Bilhar</string> <string name="poi_billiards">Bilhar</string>
<string name="poi_microwave_oven_yes">Forno microondas: sim</string> <string name="poi_microwave_oven_yes">Forno microondas: sim</string>
@ -3082,7 +3082,7 @@
<string name="poi_nutrition_supplements">Suplementos alimentares</string> <string name="poi_nutrition_supplements">Suplementos alimentares</string>
<string name="poi_photo_studio">Estúdio de fotografia</string> <string name="poi_photo_studio">Estúdio de fotografia</string>
<string name="poi_cliff">Penhasco</string> <string name="poi_cliff">Penhasco</string>
<string name="poi_animal_keeping">Cativeiro de animais;Refúgio de animais</string> <string name="poi_animal_keeping">Refúgio de animais;Cativeiro de animais</string>
<string name="poi_animal_keeping_horse">Cativeiro de animais: cavalos</string> <string name="poi_animal_keeping_horse">Cativeiro de animais: cavalos</string>
<string name="poi_animal_keeping_sheep">Cativeiro de animais: ovelhas</string> <string name="poi_animal_keeping_sheep">Cativeiro de animais: ovelhas</string>
<string name="poi_animal_keeping_type_paddock">Tipo: cercado</string> <string name="poi_animal_keeping_type_paddock">Tipo: cercado</string>
@ -3118,7 +3118,7 @@
<string name="poi_stands">Estandes</string> <string name="poi_stands">Estandes</string>
<string name="poi_motorcycle_sales_yes">Vendas</string> <string name="poi_motorcycle_sales_yes">Vendas</string>
<string name="poi_motorcycle_sales_no">Vendas: não</string> <string name="poi_motorcycle_sales_no">Vendas: não</string>
<string name="poi_motorcycle_sales_yes_used">Vendas: sim; usados</string> <string name="poi_motorcycle_sales_yes_used">Vendas: sim, usados</string>
<string name="poi_motorcycle_sales_used">Vendas: usados</string> <string name="poi_motorcycle_sales_used">Vendas: usados</string>
<string name="poi_motorcycle_rental_yes">Aluguer</string> <string name="poi_motorcycle_rental_yes">Aluguer</string>
<string name="poi_motorcycle_rental_no">Aluguer: não</string> <string name="poi_motorcycle_rental_no">Aluguer: não</string>
@ -3203,7 +3203,7 @@
<string name="poi_government_transportation">Instituição governamental de transportes</string> <string name="poi_government_transportation">Instituição governamental de transportes</string>
<string name="poi_government_legislative">Instituição legislativa governamental</string> <string name="poi_government_legislative">Instituição legislativa governamental</string>
<string name="poi_vhf">Canal VHF</string> <string name="poi_vhf">Canal VHF</string>
<string name="poi_gorge">Desfiladeiro;Canhão</string> <string name="poi_gorge">Desfiladeiro/canhão;Desfiladeiro;Canhão</string>
<string name="poi_couloir">Ravina</string> <string name="poi_couloir">Ravina</string>
<string name="poi_mountain_area">Área montanhosa</string> <string name="poi_mountain_area">Área montanhosa</string>
<string name="poi_surface_clay">Argila</string> <string name="poi_surface_clay">Argila</string>
@ -3572,7 +3572,7 @@
<string name="poi_changing_table_yes">Mesa muda-fraldas: sim</string> <string name="poi_changing_table_yes">Mesa muda-fraldas: sim</string>
<string name="poi_changing_table_no">Mesa muda-fraldas: não</string> <string name="poi_changing_table_no">Mesa muda-fraldas: não</string>
<string name="poi_changing_table_limited">Mesa muda-fraldas: limitada</string> <string name="poi_changing_table_limited">Mesa muda-fraldas: limitada</string>
<string name="poi_changing_table_location_room">Mesa muda-fraldas; sala</string> <string name="poi_changing_table_location_room">Mesa muda-fraldas: sala</string>
<string name="poi_changing_table_location_male_toilet">Local da mesa muda-fraldas: WC masculino</string> <string name="poi_changing_table_location_male_toilet">Local da mesa muda-fraldas: WC masculino</string>
<string name="poi_changing_table_location_female_toilet">Local da mesa muda-fraldas: WC feminino</string> <string name="poi_changing_table_location_female_toilet">Local da mesa muda-fraldas: WC feminino</string>
<string name="poi_changing_table_location_unisex_toilet">Local da mesa muda-fraldas: WC unissexo</string> <string name="poi_changing_table_location_unisex_toilet">Local da mesa muda-fraldas: WC unissexo</string>
@ -3810,7 +3810,7 @@
<string name="poi_traffic_signals_vibration">Vibração</string> <string name="poi_traffic_signals_vibration">Vibração</string>
<string name="poi_city_block">Quarteirão</string> <string name="poi_city_block">Quarteirão</string>
<string name="poi_borough">Município</string> <string name="poi_borough">Município</string>
<string name="poi_give_box">Caixa livre;Caixa de donativos;Give-box</string> <string name="poi_give_box">Caixa livre/de donativos;Give-box;Caixa livre;Caixa de donativos</string>
<string name="poi_traffic_signals_arrow_no">Seta: não</string> <string name="poi_traffic_signals_arrow_no">Seta: não</string>
<string name="poi_elevator">Elevador</string> <string name="poi_elevator">Elevador</string>
<string name="poi_departures_board_timetable">Horário</string> <string name="poi_departures_board_timetable">Horário</string>
@ -3917,8 +3917,8 @@
<string name="poi_mobile_library">Posição de paragem da biblioteca itinerante</string> <string name="poi_mobile_library">Posição de paragem da biblioteca itinerante</string>
<string name="poi_piste_status_closed">Estado da pista: fechada</string> <string name="poi_piste_status_closed">Estado da pista: fechada</string>
<string name="poi_piste_status_open">Estado da pista: aberta</string> <string name="poi_piste_status_open">Estado da pista: aberta</string>
<string name="poi_patrolled_no">Vigiado: não</string> <string name="poi_patrolled_no">Supervisionado: não</string>
<string name="poi_patrolled_yes">Vigiado: sim</string> <string name="poi_patrolled_yes">Supervisionado: sim</string>
<string name="poi_piste_name">Nome da pista</string> <string name="poi_piste_name">Nome da pista</string>
<string name="poi_piste_ski_jump">Salto com esqui</string> <string name="poi_piste_ski_jump">Salto com esqui</string>
<string name="poi_wildlife_crossing">Passagem de vida selvagem</string> <string name="poi_wildlife_crossing">Passagem de vida selvagem</string>

View file

@ -371,7 +371,7 @@
<string name="poi_error_io_error_template">Erro de entrada/saída na execução da ação {0}.</string> <string name="poi_error_io_error_template">Erro de entrada/saída na execução da ação {0}.</string>
<string name="poi_error_info_not_loaded">As informações sobre o objeto não foram carregadas</string> <string name="poi_error_info_not_loaded">As informações sobre o objeto não foram carregadas</string>
<string name="poi_dialog_opening_hours">Aberto</string> <string name="poi_dialog_opening_hours">Aberto</string>
<string name="poi_dialog_comment">Comentário</string> <string name="poi_dialog_comment">Comentar</string>
<string name="poi_dialog_comment_default">Alterar POI</string> <string name="poi_dialog_comment_default">Alterar POI</string>
<string name="poi_dialog_other_tags_message">Todas as outras etiquetas são preservadas</string> <string name="poi_dialog_other_tags_message">Todas as outras etiquetas são preservadas</string>
<string name="default_buttons_commit">Enviar</string> <string name="default_buttons_commit">Enviar</string>
@ -920,7 +920,7 @@
<string name="srtm_plugin_name">Curvas de nível</string> <string name="srtm_plugin_name">Curvas de nível</string>
<string name="download_select_map_types">Outros mapas</string> <string name="download_select_map_types">Outros mapas</string>
<string name="download_srtm_maps">Curvas de nível</string> <string name="download_srtm_maps">Curvas de nível</string>
<string name="rendering_attr_noAdminboundaries_name">Limites</string> <string name="rendering_attr_noAdminboundaries_name">Fronteiras regionais</string>
<string name="rendering_attr_noAdminboundaries_description">Ocultar a visualização de limites regionais (níveis de administração 5 9).</string> <string name="rendering_attr_noAdminboundaries_description">Ocultar a visualização de limites regionais (níveis de administração 5 9).</string>
<string name="recording_context_menu_show">Ver</string> <string name="recording_context_menu_show">Ver</string>
<string name="item_unchecked">desmarcado</string> <string name="item_unchecked">desmarcado</string>
@ -1018,7 +1018,7 @@
<string name="av_camera_focus_auto">Focagem automática</string> <string name="av_camera_focus_auto">Focagem automática</string>
<string name="av_camera_focus_hiperfocal">Foco hiperfocal</string> <string name="av_camera_focus_hiperfocal">Foco hiperfocal</string>
<string name="av_camera_focus_edof">Profundidade de campo alargada (EDOF)</string> <string name="av_camera_focus_edof">Profundidade de campo alargada (EDOF)</string>
<string name="av_camera_focus_infinity">O foco está definido como infinito</string> <string name="av_camera_focus_infinity">Focar infinito</string>
<string name="av_camera_focus_macro">Focagem macro (close-up)</string> <string name="av_camera_focus_macro">Focagem macro (close-up)</string>
<string name="av_camera_focus_continuous">A câmara tenta focar continuadamente</string> <string name="av_camera_focus_continuous">A câmara tenta focar continuadamente</string>
<string name="av_photo_play_sound">Reproduzir o som do obturador da câmara</string> <string name="av_photo_play_sound">Reproduzir o som do obturador da câmara</string>
@ -1435,7 +1435,7 @@
<string name="rendering_attr_coloredBuildings_name">Colorir edifícios por tipo</string> <string name="rendering_attr_coloredBuildings_name">Colorir edifícios por tipo</string>
<string name="rendering_attr_alpineHiking_name">Escala de montanhismo (SAC)</string> <string name="rendering_attr_alpineHiking_name">Escala de montanhismo (SAC)</string>
<string name="rendering_attr_hikingRoutesOSMC_name">Camada superior de símbolos de montanhismo</string> <string name="rendering_attr_hikingRoutesOSMC_name">Camada superior de símbolos de montanhismo</string>
<string name="rendering_attr_showCycleRoutes_name">Mostrar rotas para bicicletas</string> <string name="rendering_attr_showCycleRoutes_name">Rotas para bicicletas</string>
<string name="rendering_attr_hideBuildings_name">Edifícios</string> <string name="rendering_attr_hideBuildings_name">Edifícios</string>
<string name="rendering_attr_trolleybusRoutes_name">Rotas de troleicarros</string> <string name="rendering_attr_trolleybusRoutes_name">Rotas de troleicarros</string>
<string name="favorite_category_dublicate_message">Por favor, use um nome de categoria que ainda não exista.</string> <string name="favorite_category_dublicate_message">Por favor, use um nome de categoria que ainda não exista.</string>
@ -1513,7 +1513,7 @@
<string name="share_osm_edits_subject">Edições OSM partilhadas via OsmAnd</string> <string name="share_osm_edits_subject">Edições OSM partilhadas via OsmAnd</string>
<string name="read_more">Ler mais</string> <string name="read_more">Ler mais</string>
<string name="whats_new">Novidades</string> <string name="whats_new">Novidades</string>
<string name="rendering_attr_hideProposed_name">Objetos planeados</string> <string name="rendering_attr_hideProposed_name">Elementos com construção planeada</string>
<string name="shared_string_upload">Enviar</string> <string name="shared_string_upload">Enviar</string>
<string name="osm_edit_created_poi">POI OSM adicionado</string> <string name="osm_edit_created_poi">POI OSM adicionado</string>
<string name="world_map_download_descr">Mapa base mundial (cobrindo o mundo inteiro em baixo nível de ampliação) ausente ou ultrapassado. Por favor, considere descarregá-lo para uma visão global.</string> <string name="world_map_download_descr">Mapa base mundial (cobrindo o mundo inteiro em baixo nível de ampliação) ausente ou ultrapassado. Por favor, considere descarregá-lo para uma visão global.</string>
@ -1754,7 +1754,7 @@
<string name="map_markers">Marcadores</string> <string name="map_markers">Marcadores</string>
<string name="map_marker">Marcador de mapa</string> <string name="map_marker">Marcador de mapa</string>
<string name="consider_turning_polygons_off">É recomendável desativar a renderização de polígono.</string> <string name="consider_turning_polygons_off">É recomendável desativar a renderização de polígono.</string>
<string name="rendering_attr_showMtbRoutes_name">Mostrar trilhos de bicicletas de montanha</string> <string name="rendering_attr_showMtbRoutes_name">Trilhos de bicicletas BTT</string>
<string name="show_polygons">Mostrar polígonos</string> <string name="show_polygons">Mostrar polígonos</string>
<string name="find_parking">Encontrar estacionamento</string> <string name="find_parking">Encontrar estacionamento</string>
<string name="shared_string_status">Situação</string> <string name="shared_string_status">Situação</string>
@ -1768,7 +1768,7 @@
<string name="road_blocked">Estrada bloqueada</string> <string name="road_blocked">Estrada bloqueada</string>
<string name="shared_string_select">Selecionar</string> <string name="shared_string_select">Selecionar</string>
<string name="switch_start_finish">Inverter ponto de partida e destino</string> <string name="switch_start_finish">Inverter ponto de partida e destino</string>
<string name="rendering_attr_hideIcons_name">Ícones POI</string> <string name="rendering_attr_hideIcons_name">Ícones dos POI</string>
<string name="item_removed">Item removido</string> <string name="item_removed">Item removido</string>
<string name="n_items_removed">Itens removidos</string> <string name="n_items_removed">Itens removidos</string>
<string name="shared_string_undo_all">Desfazer tudo</string> <string name="shared_string_undo_all">Desfazer tudo</string>
@ -1776,7 +1776,7 @@
<string name="starting_point">Ponto de partida</string> <string name="starting_point">Ponto de partida</string>
<string name="rec_split">Divisão das gravações</string> <string name="rec_split">Divisão das gravações</string>
<string name="rec_split_title">Usar divisão das gravações</string> <string name="rec_split_title">Usar divisão das gravações</string>
<string name="rec_split_clip_length">Duração do recorte</string> <string name="rec_split_clip_length">Duração da divisão</string>
<string name="rec_split_clip_length_desc">Limite de tempo máximo para clipes gravados.</string> <string name="rec_split_clip_length_desc">Limite de tempo máximo para clipes gravados.</string>
<string name="lang_mk">Macedónio</string> <string name="lang_mk">Macedónio</string>
<string name="lang_sh">Servo-Croata</string> <string name="lang_sh">Servo-Croata</string>
@ -2026,7 +2026,7 @@
<string name="rendering_attr_contourWidth_description">Espessura das curvas de nível</string> <string name="rendering_attr_contourWidth_description">Espessura das curvas de nível</string>
<string name="rendering_attr_contourWidth_name">Espessura das curvas de nível</string> <string name="rendering_attr_contourWidth_name">Espessura das curvas de nível</string>
<string name="rendering_attr_hideWaterPolygons_description">Água</string> <string name="rendering_attr_hideWaterPolygons_description">Água</string>
<string name="rendering_attr_hideWaterPolygons_name">Ocultar água</string> <string name="rendering_attr_hideWaterPolygons_name">Corpos de água largos</string>
<string name="routing_attr_allow_motorway_name">Utilizar autoestradas</string> <string name="routing_attr_allow_motorway_name">Utilizar autoestradas</string>
<string name="routing_attr_allow_motorway_description">Permitir autoestradas.</string> <string name="routing_attr_allow_motorway_description">Permitir autoestradas.</string>
<string name="wiki_around">Artigos da Wikipédia próximos</string> <string name="wiki_around">Artigos da Wikipédia próximos</string>
@ -2127,7 +2127,7 @@
<string name="get_osmand_live">Adquira o OsmAnd Live para desbloquear todas as funcionalidades: atualizações diárias de mapas com descarregamentos ilimitados, todas as extensões pagas e gratuitas, Wikipédia, Wikivoyage e muito mais.</string> <string name="get_osmand_live">Adquira o OsmAnd Live para desbloquear todas as funcionalidades: atualizações diárias de mapas com descarregamentos ilimitados, todas as extensões pagas e gratuitas, Wikipédia, Wikivoyage e muito mais.</string>
<string name="unirs_render_descr">Alteração do estilo padrão para aumentar o contraste de caminhos pedestres e ciclovias. Usa cores clássicas do Mapnik.</string> <string name="unirs_render_descr">Alteração do estilo padrão para aumentar o contraste de caminhos pedestres e ciclovias. Usa cores clássicas do Mapnik.</string>
<string name="shared_string_bookmark">Favorito</string> <string name="shared_string_bookmark">Favorito</string>
<string name="hide_full_description">Esconder descrição completa</string> <string name="hide_full_description">Ocultar descrição completa</string>
<string name="show_full_description">Mostrar a descrição completa</string> <string name="show_full_description">Mostrar a descrição completa</string>
<string name="thank_you_for_feedback">Obrigado pelos seus comentários</string> <string name="thank_you_for_feedback">Obrigado pelos seus comentários</string>
<string name="search_street">Procurar rua</string> <string name="search_street">Procurar rua</string>
@ -2261,7 +2261,7 @@
<string name="first_intermediate_dest_description">Adicionar paragem inicial</string> <string name="first_intermediate_dest_description">Adicionar paragem inicial</string>
<string name="subsequent_dest_description">Mover destino para cima e criar destino</string> <string name="subsequent_dest_description">Mover destino para cima e criar destino</string>
<string name="show_closed_notes">Mostrar notas fechadas</string> <string name="show_closed_notes">Mostrar notas fechadas</string>
<string name="switch_osm_notes_visibility_desc">Mostrar ou ocultar notas do OpenStreetMap no mapa.</string> <string name="switch_osm_notes_visibility_desc">Mostrar notas do OpenStreetMap.</string>
<string name="gpx_file_desc">GPX - adequado para exportar para o JOSM ou outros editores do OSM.</string> <string name="gpx_file_desc">GPX - adequado para exportar para o JOSM ou outros editores do OSM.</string>
<string name="osc_file_desc">OSC - adequado para exportar para o OSM.</string> <string name="osc_file_desc">OSC - adequado para exportar para o OSM.</string>
<string name="shared_string_gpx_file">Ficheiro GPX</string> <string name="shared_string_gpx_file">Ficheiro GPX</string>
@ -3293,7 +3293,7 @@
<string name="empty_filename">O nome do ficheiro está vazio</string> <string name="empty_filename">O nome do ficheiro está vazio</string>
<string name="shared_string_revert">Reverter</string> <string name="shared_string_revert">Reverter</string>
<string name="quick_action_directions_from_desc">Um botão para centrar o ecrã no ponto de partida. Em seguida, solicitará para definir o destino ou acionar o cálculo da rota.</string> <string name="quick_action_directions_from_desc">Um botão para centrar o ecrã no ponto de partida. Em seguida, solicitará para definir o destino ou acionar o cálculo da rota.</string>
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Mostrar nós da rede de ciclovias</string> <string name="rendering_attr_showCycleNodeNetworkRoutes_name">Nós da rede de ciclovias</string>
<string name="clear_confirmation_msg">Limpar %1$s\?</string> <string name="clear_confirmation_msg">Limpar %1$s\?</string>
<string name="download_map_dialog">Diálogo de descarregar mapas</string> <string name="download_map_dialog">Diálogo de descarregar mapas</string>
<string name="dialogs_and_notifications_title">Diálogos e notificações</string> <string name="dialogs_and_notifications_title">Diálogos e notificações</string>
@ -3442,7 +3442,7 @@
<string name="osm_authorization_success">Autorização bem sucedida</string> <string name="osm_authorization_success">Autorização bem sucedida</string>
<string name="multimedia_photo_play_sound">Som do obturador da câmara</string> <string name="multimedia_photo_play_sound">Som do obturador da câmara</string>
<string name="multimedia_use_system_camera">Usar aplicação do sistema</string> <string name="multimedia_use_system_camera">Usar aplicação do sistema</string>
<string name="multimedia_rec_split_title">Divisão de gravação</string> <string name="multimedia_rec_split_title">Dividir gravações</string>
<string name="reset_plugin_to_default">Repor configurações originais da extensão</string> <string name="reset_plugin_to_default">Repor configurações originais da extensão</string>
<string name="monitoring_min_distance">Deslocamento mínimo</string> <string name="monitoring_min_distance">Deslocamento mínimo</string>
<string name="monitoring_min_accuracy">Precisão mínima</string> <string name="monitoring_min_accuracy">Precisão mínima</string>
@ -3454,7 +3454,7 @@
<string name="live_monitoring_time_buffer">Memória intermédia</string> <string name="live_monitoring_time_buffer">Memória intermédia</string>
<string name="monitoring_min_distance_descr_recommendation">Recomendação: uma configuração de 5 metros pode funcionar bem se não precisar capturar detalhes mais refinados do que isso e não quer capturar dados explicitamente enquanto estiver parado.</string> <string name="monitoring_min_distance_descr_recommendation">Recomendação: uma configuração de 5 metros pode funcionar bem se não precisar capturar detalhes mais refinados do que isso e não quer capturar dados explicitamente enquanto estiver parado.</string>
<string name="monitoring_min_distance_descr_side_effect">Efeitos colaterais: os períodos em que está parado não são registados em absoluto ou em apenas um ponto cada. Pequenos movimentos (no mundo real, por exemplo de lado, para marcar um possível desvio na sua viagem) podem ser filtrados. O seu ficheiro contém menos informações para pós-processamento e possui estatísticas piores ao filtrar pontos obviamente redundantes no tempo de gravação, mantendo potencialmente os artefactos causados por má receção ou efeitos do chipset GPS.</string> <string name="monitoring_min_distance_descr_side_effect">Efeitos colaterais: os períodos em que está parado não são registados em absoluto ou em apenas um ponto cada. Pequenos movimentos (no mundo real, por exemplo de lado, para marcar um possível desvio na sua viagem) podem ser filtrados. O seu ficheiro contém menos informações para pós-processamento e possui estatísticas piores ao filtrar pontos obviamente redundantes no tempo de gravação, mantendo potencialmente os artefactos causados por má receção ou efeitos do chipset GPS.</string>
<string name="monitoring_min_distance_descr">Este filtro evita que sejam gravados pontos duplicados onde ocorrer muito pouco movimento real, cria uma aparência espacial mais agradável dos trilhos que não são processados posteriormente.</string> <string name="monitoring_min_distance_descr">Este filtro evita que sejam gravados pontos duplicados quando houver muito pouco movimento real e cria uma aparência espacial mais agradável dos trilhos que não são processados posteriormente.</string>
<string name="monitoring_min_accuracy_descr_remark">Observação: se o GPS estava desligado imediatamente antes de uma gravação, o primeiro ponto medido pode ter uma precisão diminuída; portanto, no nosso código, podemos esperar um segundo antes da gravação de um ponto (ou gravar o melhor de três pontos consecutivos, etc.), mas isso ainda não foi implementado.</string> <string name="monitoring_min_accuracy_descr_remark">Observação: se o GPS estava desligado imediatamente antes de uma gravação, o primeiro ponto medido pode ter uma precisão diminuída; portanto, no nosso código, podemos esperar um segundo antes da gravação de um ponto (ou gravar o melhor de três pontos consecutivos, etc.), mas isso ainda não foi implementado.</string>
<string name="monitoring_min_accuracy_descr_recommendation">Recomendação: é difícil prever o que será gravado e o que não será, talvez seja melhor desativar este filtro.</string> <string name="monitoring_min_accuracy_descr_recommendation">Recomendação: é difícil prever o que será gravado e o que não será, talvez seja melhor desativar este filtro.</string>
<string name="monitoring_min_accuracy_descr_side_effect">Efeito colateral: como resultado da filtragem por precisão, os pontos podem estar totalmente ausentes por ex. debaixo de pontes, sob árvores, entre prédios altos ou com certas condições climáticas.</string> <string name="monitoring_min_accuracy_descr_side_effect">Efeito colateral: como resultado da filtragem por precisão, os pontos podem estar totalmente ausentes por ex. debaixo de pontes, sob árvores, entre prédios altos ou com certas condições climáticas.</string>
@ -3501,7 +3501,7 @@
<string name="settings_item_import_error">Não foi possível importar de \'%1$s\'.</string> <string name="settings_item_import_error">Não foi possível importar de \'%1$s\'.</string>
<string name="ui_customization_description">Personalize a quantidade de itens em \"Gaveta\", \"Configurar Mapa\" e \"Menu de Contexto\". <string name="ui_customization_description">Personalize a quantidade de itens em \"Gaveta\", \"Configurar Mapa\" e \"Menu de Contexto\".
\n \n
\nDesative as extensões não utilizados para ocultar todos os seus controlos. %1$s.</string> \nDesative as extensões não utilizadas para ocultar todos os seus controlos. %1$s.</string>
<string name="ui_customization_short_descr">Itens da gaveta, menu de contexto</string> <string name="ui_customization_short_descr">Itens da gaveta, menu de contexto</string>
<string name="ui_customization">Personalização da interface</string> <string name="ui_customization">Personalização da interface</string>
<string name="shared_string_drawer">Gaveta</string> <string name="shared_string_drawer">Gaveta</string>
@ -3704,7 +3704,7 @@
<string name="add_to_a_track">Adicionar a um trilho</string> <string name="add_to_a_track">Adicionar a um trilho</string>
<string name="add_hidden_group_info">O ponto adicionado não será visível no mapa, já que o grupo selecionado está escondido, pode encontrá-lo em \"%s\".</string> <string name="add_hidden_group_info">O ponto adicionado não será visível no mapa, já que o grupo selecionado está escondido, pode encontrá-lo em \"%s\".</string>
<string name="track_show_start_finish_icons">Mostrar ícones de início e fim</string> <string name="track_show_start_finish_icons">Mostrar ícones de início e fim</string>
<string name="select_track_width">Selecionar espessura da linha do trilho</string> <string name="select_track_width">Espessura da linha do trilho</string>
<string name="gpx_split_interval_descr">Selecione o intervalo em que as marcas com distância ou tempo no trilho serão mostradas.</string> <string name="gpx_split_interval_descr">Selecione o intervalo em que as marcas com distância ou tempo no trilho serão mostradas.</string>
<string name="gpx_split_interval_none_descr">Selecione a opção de divisão desejada: por tempo ou por distância.</string> <string name="gpx_split_interval_none_descr">Selecione a opção de divisão desejada: por tempo ou por distância.</string>
<string name="shared_string_custom">Personalizado</string> <string name="shared_string_custom">Personalizado</string>
@ -4025,7 +4025,7 @@
<string name="no_purchases">Não tem compras</string> <string name="no_purchases">Não tem compras</string>
<string name="contact_support_description">Se tiver alguma dúvida, contacte-nos em %1$s.</string> <string name="contact_support_description">Se tiver alguma dúvida, contacte-nos em %1$s.</string>
<string name="announcement_time_intervals">Intervalos de tempo e distância</string> <string name="announcement_time_intervals">Intervalos de tempo e distância</string>
<string name="map_widget_distance_by_tap">Distância por toque</string> <string name="map_widget_distance_by_tap">Distância com 2 dedos</string>
<string name="contact_support">Contacte o suporte</string> <string name="contact_support">Contacte o suporte</string>
<string name="troubleshooting_description">Por favor siga este link se tiver algum problema com assinaturas.</string> <string name="troubleshooting_description">Por favor siga este link se tiver algum problema com assinaturas.</string>
<string name="update_all_maps_added">Atualizar todos os mapas para %1$s\?</string> <string name="update_all_maps_added">Atualizar todos os mapas para %1$s\?</string>

View file

@ -3,10 +3,10 @@ package net.osmand;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
@ -38,6 +38,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
public class AndroidNetworkUtils { public class AndroidNetworkUtils {
@ -56,6 +57,15 @@ public class AndroidNetworkUtils {
void onFilesUploadDone(@NonNull Map<File, String> errors); void onFilesUploadDone(@NonNull Map<File, String> errors);
} }
public interface OnFilesDownloadCallback {
@Nullable
Map<String, String> getAdditionalParams(@NonNull File file);
void onFileDownloadProgress(@NonNull File file, int percent);
@WorkerThread
void onFileDownloadedAsync(@NonNull File file);
void onFilesDownloadDone(@NonNull Map<File, String> errors);
}
public static class RequestResponse { public static class RequestResponse {
private Request request; private Request request;
private String response; private String response;
@ -74,35 +84,46 @@ public class AndroidNetworkUtils {
} }
} }
public interface OnRequestsResultListener { public interface OnSendRequestsListener {
void onResult(@NonNull List<RequestResponse> results); void onRequestSent(@NonNull RequestResponse response);
void onRequestsSent(@NonNull List<RequestResponse> results);
} }
public static void sendRequestsAsync(final OsmandApplication ctx, public static void sendRequestsAsync(@Nullable final OsmandApplication ctx,
final List<Request> requests, @NonNull final List<Request> requests,
final OnRequestsResultListener listener) { @Nullable final OnSendRequestsListener listener) {
new AsyncTask<Void, Void, List<RequestResponse>>() { new AsyncTask<Void, RequestResponse, List<RequestResponse>>() {
@Override @Override
protected List<RequestResponse> doInBackground(Void... params) { protected List<RequestResponse> doInBackground(Void... params) {
List<RequestResponse> responses = new ArrayList<>(); List<RequestResponse> responses = new ArrayList<>();
for (Request request : requests) { for (Request request : requests) {
RequestResponse requestResponse;
try { try {
String response = sendRequest(ctx, request.getUrl(), request.getParameters(), String response = sendRequest(ctx, request.getUrl(), request.getParameters(),
request.getUserOperation(), request.isToastAllowed(), request.isPost()); request.getUserOperation(), request.isToastAllowed(), request.isPost());
responses.add(new RequestResponse(request, response)); requestResponse = new RequestResponse(request, response);
} catch (Exception e) { } catch (Exception e) {
responses.add(new RequestResponse(request, null)); requestResponse = new RequestResponse(request, null);
} }
responses.add(requestResponse);
publishProgress(requestResponse);
} }
return responses; return responses;
} }
@Override
protected void onProgressUpdate(RequestResponse... values) {
if (listener != null) {
listener.onRequestSent(values[0]);
}
}
@Override @Override
protected void onPostExecute(@NonNull List<RequestResponse> results) { protected void onPostExecute(@NonNull List<RequestResponse> results) {
if (listener != null) { if (listener != null) {
listener.onResult(results); listener.onRequestsSent(results);
} }
} }
@ -146,7 +167,7 @@ public class AndroidNetworkUtils {
@Override @Override
protected String doInBackground(Void... params) { protected String doInBackground(Void... params) {
return downloadFile(url, fileToSave); return downloadFile(url, fileToSave, false, null);
} }
@Override @Override
@ -158,8 +179,80 @@ public class AndroidNetworkUtils {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
} }
public static String sendRequest(OsmandApplication ctx, String url, Map<String, String> parameters, public static void downloadFilesAsync(final @NonNull String url,
String userOperation, boolean toastAllowed, boolean post) { final @NonNull List<File> files,
final @NonNull Map<String, String> parameters,
final @Nullable OnFilesDownloadCallback callback) {
new AsyncTask<Void, Object, Map<File, String>>() {
@Override
@NonNull
protected Map<File, String> doInBackground(Void... v) {
Map<File, String> errors = new HashMap<>();
for (final File file : files) {
final int[] progressValue = {0};
publishProgress(file, 0);
IProgress progress = null;
if (callback != null) {
progress = new NetworkProgress() {
@Override
public void progress(int deltaWork) {
progressValue[0] += deltaWork;
publishProgress(file, progressValue[0]);
}
};
}
try {
Map<String, String> params = new HashMap<>(parameters);
if (callback != null) {
Map<String, String> additionalParams = callback.getAdditionalParams(file);
if (additionalParams != null) {
params.putAll(additionalParams);
}
}
boolean firstPrm = !url.contains("?");
StringBuilder sb = new StringBuilder(url);
for (Entry<String, String> entry : params.entrySet()) {
sb.append(firstPrm ? "?" : "&").append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
firstPrm = false;
}
String res = downloadFile(sb.toString(), file, true, progress);
if (res != null) {
errors.put(file, res);
} else {
if (callback != null) {
callback.onFileDownloadedAsync(file);
}
}
} catch (Exception e) {
errors.put(file, e.getMessage());
}
publishProgress(file, Integer.MAX_VALUE);
}
return errors;
}
@Override
protected void onProgressUpdate(Object... objects) {
if (callback != null) {
callback.onFileDownloadProgress((File) objects[0], (Integer) objects[1]);
}
}
@Override
protected void onPostExecute(@NonNull Map<File, String> errors) {
if (callback != null) {
callback.onFilesDownloadDone(errors);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
public static String sendRequest(@Nullable OsmandApplication ctx, @NonNull String url,
@Nullable Map<String, String> parameters,
@Nullable String userOperation, boolean toastAllowed, boolean post) {
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
@ -177,7 +270,7 @@ public class AndroidNetworkUtils {
String paramsSeparator = url.indexOf('?') == -1 ? "?" : "&"; String paramsSeparator = url.indexOf('?') == -1 ? "?" : "&";
connection = NetworkUtils.getHttpURLConnection(params == null || post ? url : url + paramsSeparator + params); connection = NetworkUtils.getHttpURLConnection(params == null || post ? url : url + paramsSeparator + params);
connection.setRequestProperty("Accept-Charset", "UTF-8"); connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); connection.setRequestProperty("User-Agent", ctx != null ? Version.getFullVersion(ctx) : "OsmAnd");
connection.setConnectTimeout(15000); connection.setConnectTimeout(15000);
if (params != null && post) { if (params != null && post) {
connection.setDoInput(true); connection.setDoInput(true);
@ -200,9 +293,10 @@ public class AndroidNetworkUtils {
} }
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
if (toastAllowed) { if (toastAllowed && ctx != null) {
String msg = userOperation String msg = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "")
+ " " + ctx.getString(R.string.failed_op) + ": " + connection.getResponseMessage(); + ctx.getString(R.string.failed_op) + ": "
+ connection.getResponseMessage();
showToast(ctx, msg); showToast(ctx, msg);
} }
} else { } else {
@ -233,17 +327,17 @@ public class AndroidNetworkUtils {
} catch (NullPointerException e) { } catch (NullPointerException e) {
// that's tricky case why NPE is thrown to fix that problem httpClient could be used // that's tricky case why NPE is thrown to fix that problem httpClient could be used
if (toastAllowed) { if (toastAllowed && ctx != null) {
String msg = ctx.getString(R.string.auth_failed); String msg = ctx.getString(R.string.auth_failed);
showToast(ctx, msg); showToast(ctx, msg);
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
if (toastAllowed) { if (toastAllowed && ctx != null) {
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
} }
} catch (IOException e) { } catch (IOException e) {
if (toastAllowed) { if (toastAllowed && ctx != null) {
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation)); + ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation));
} }
@ -277,18 +371,23 @@ public class AndroidNetworkUtils {
return res; return res;
} }
public static String downloadFile(@NonNull String url, @NonNull File fileToSave) { public static String downloadFile(@NonNull String url, @NonNull File fileToSave, boolean gzip, @Nullable IProgress progress) {
String error = null; String error = null;
try { try {
URLConnection connection = NetworkUtils.getHttpURLConnection(url); URLConnection connection = NetworkUtils.getHttpURLConnection(url);
connection.setConnectTimeout(CONNECTION_TIMEOUT); connection.setConnectTimeout(CONNECTION_TIMEOUT);
connection.setReadTimeout(CONNECTION_TIMEOUT); connection.setReadTimeout(CONNECTION_TIMEOUT);
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream(), 8 * 1024); if (gzip) {
connection.setRequestProperty("Accept-Encoding", "deflate, gzip");
}
InputStream inputStream = gzip
? new GZIPInputStream(connection.getInputStream())
: new BufferedInputStream(connection.getInputStream(), 8 * 1024);
fileToSave.getParentFile().mkdirs(); fileToSave.getParentFile().mkdirs();
OutputStream stream = null; OutputStream stream = null;
try { try {
stream = new FileOutputStream(fileToSave); stream = new FileOutputStream(fileToSave);
Algorithms.streamCopy(inputStream, stream); Algorithms.streamCopy(inputStream, stream, progress, 1024);
stream.flush(); stream.flush();
} finally { } finally {
Algorithms.closeStream(inputStream); Algorithms.closeStream(inputStream);
@ -307,12 +406,17 @@ public class AndroidNetworkUtils {
private static final String BOUNDARY = "CowMooCowMooCowCowCow"; private static final String BOUNDARY = "CowMooCowMooCowCowCow";
public static String uploadFile(@NonNull String urlText, @NonNull File file, boolean gzip, public static String uploadFile(@NonNull String urlText, @NonNull File file, boolean gzip,
@NonNull Map<String, String> additionalParams, @Nullable Map<String, String> headers) throws IOException { @NonNull Map<String, String> additionalParams,
return uploadFile(urlText, new FileInputStream(file), file.getName(), gzip, additionalParams, headers); @Nullable Map<String, String> headers,
@Nullable IProgress progress) throws IOException {
return uploadFile(urlText, new FileInputStream(file), file.getName(), gzip, additionalParams, headers, progress);
} }
public static String uploadFile(@NonNull String urlText, @NonNull InputStream inputStream, @NonNull String fileName, boolean gzip, public static String uploadFile(@NonNull String urlText, @NonNull InputStream inputStream,
Map<String, String> additionalParams, @Nullable Map<String, String> headers) { @NonNull String fileName, boolean gzip,
@NonNull Map<String, String> additionalParams,
@Nullable Map<String, String> headers,
@Nullable IProgress progress) {
URL url; URL url;
try { try {
boolean firstPrm = !urlText.contains("?"); boolean firstPrm = !urlText.contains("?");
@ -350,11 +454,11 @@ public class AndroidNetworkUtils {
ous.flush(); ous.flush();
if (gzip) { if (gzip) {
GZIPOutputStream gous = new GZIPOutputStream(ous, 1024); GZIPOutputStream gous = new GZIPOutputStream(ous, 1024);
Algorithms.streamCopy(bis, gous); Algorithms.streamCopy(bis, gous, progress, 1024);
gous.flush(); gous.flush();
gous.finish(); gous.finish();
} else { } else {
Algorithms.streamCopy(bis, ous); Algorithms.streamCopy(bis, ous, progress, 1024);
} }
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes());
@ -406,8 +510,19 @@ public class AndroidNetworkUtils {
@NonNull @NonNull
protected Map<File, String> doInBackground(Void... v) { protected Map<File, String> doInBackground(Void... v) {
Map<File, String> errors = new HashMap<>(); Map<File, String> errors = new HashMap<>();
for (File file : files) { for (final File file : files) {
final int[] progressValue = {0};
publishProgress(file, 0); publishProgress(file, 0);
IProgress progress = null;
if (callback != null) {
progress = new NetworkProgress() {
@Override
public void progress(int deltaWork) {
progressValue[0] += deltaWork;
publishProgress(file, progressValue[0]);
}
};
}
try { try {
Map<String, String> params = new HashMap<>(parameters); Map<String, String> params = new HashMap<>(parameters);
if (callback != null) { if (callback != null) {
@ -416,14 +531,14 @@ public class AndroidNetworkUtils {
params.putAll(additionalParams); params.putAll(additionalParams);
} }
} }
String res = uploadFile(url, file, gzip, params, headers); String res = uploadFile(url, file, gzip, params, headers, progress);
if (res != null) { if (res != null) {
errors.put(file, res); errors.put(file, res);
} }
} catch (Exception e) { } catch (Exception e) {
errors.put(file, e.getMessage()); errors.put(file, e.getMessage());
} }
publishProgress(file, 100); publishProgress(file, Integer.MAX_VALUE);
} }
return errors; return errors;
} }
@ -484,4 +599,39 @@ public class AndroidNetworkUtils {
return post; return post;
} }
} }
private abstract static class NetworkProgress implements IProgress {
@Override
public void startTask(String taskName, int work) {
}
@Override
public void startWork(int work) {
}
@Override
public abstract void progress(int deltaWork);
@Override
public void remaining(int remainingWork) {
}
@Override
public void finishTask() {
}
@Override
public boolean isIndeterminate() {
return false;
}
@Override
public boolean isInterrupted() {
return false;
}
@Override
public void setGeneralProgress(String genProgress) {
}
}
} }

View file

@ -184,7 +184,8 @@ public class AnalyticsHelper extends SQLiteOpenHelper {
String jsonStr = json.toString(); String jsonStr = json.toString();
InputStream inputStream = new ByteArrayInputStream(jsonStr.getBytes()); InputStream inputStream = new ByteArrayInputStream(jsonStr.getBytes());
String res = AndroidNetworkUtils.uploadFile(ANALYTICS_UPLOAD_URL, inputStream, ANALYTICS_FILE_NAME, true, additionalData, null); String res = AndroidNetworkUtils.uploadFile(ANALYTICS_UPLOAD_URL, inputStream,
ANALYTICS_FILE_NAME, true, additionalData, null, null);
if (res != null) { if (res != null) {
return; return;
} }

View file

@ -31,6 +31,7 @@ import net.osmand.osm.MapPoiTypes;
import net.osmand.plus.activities.LocalIndexHelper; import net.osmand.plus.activities.LocalIndexHelper;
import net.osmand.plus.activities.LocalIndexInfo; import net.osmand.plus.activities.LocalIndexInfo;
import net.osmand.plus.activities.SavingTrackHelper; import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.backup.BackupHelper;
import net.osmand.plus.base.MapViewTrackingUtilities; import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.download.DownloadActivity; import net.osmand.plus.download.DownloadActivity;
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask; import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
@ -473,6 +474,7 @@ public class AppInitializer implements IProgress {
app.oprAuthHelper = startupInit(new OprAuthHelper(app), OprAuthHelper.class); app.oprAuthHelper = startupInit(new OprAuthHelper(app), OprAuthHelper.class);
app.onlineRoutingHelper = startupInit(new OnlineRoutingHelper(app), OnlineRoutingHelper.class); app.onlineRoutingHelper = startupInit(new OnlineRoutingHelper(app), OnlineRoutingHelper.class);
app.itineraryHelper = startupInit(new ItineraryHelper(app), ItineraryHelper.class); app.itineraryHelper = startupInit(new ItineraryHelper(app), ItineraryHelper.class);
app.backupHelper = startupInit(new BackupHelper(app), BackupHelper.class);
initOpeningHoursParser(); initOpeningHoursParser();
} }

View file

@ -146,6 +146,14 @@ public class FavouritesDbHelper {
} }
} }
public long getLastUploadedTime() {
return context.getSettings().FAVORITES_LAST_UPLOADED_TIME.get();
}
public void setLastUploadedTime(long time) {
context.getSettings().FAVORITES_LAST_UPLOADED_TIME.set(time);
}
@Nullable @Nullable
public Drawable getColoredIconForGroup(String groupName) { public Drawable getColoredIconForGroup(String groupName) {
String groupIdName = FavoriteGroup.convertDisplayNameToGroupIdName(context, groupName); String groupIdName = FavoriteGroup.convertDisplayNameToGroupIdName(context, groupName);

View file

@ -18,7 +18,7 @@ import androidx.annotation.Nullable;
public class GPXDatabase { public class GPXDatabase {
private static final int DB_VERSION = 11; private static final int DB_VERSION = 12;
private static final String DB_NAME = "gpx_database"; private static final String DB_NAME = "gpx_database";
private static final String GPX_TABLE_NAME = "gpxTable"; private static final String GPX_TABLE_NAME = "gpxTable";
@ -48,6 +48,7 @@ public class GPXDatabase {
private static final String GPX_COL_COLOR = "color"; private static final String GPX_COL_COLOR = "color";
private static final String GPX_COL_FILE_LAST_MODIFIED_TIME = "fileLastModifiedTime"; private static final String GPX_COL_FILE_LAST_MODIFIED_TIME = "fileLastModifiedTime";
private static final String GPX_COL_FILE_LAST_UPLOADED_TIME = "fileLastUploadedTime";
private static final String GPX_COL_SPLIT_TYPE = "splitType"; private static final String GPX_COL_SPLIT_TYPE = "splitType";
private static final String GPX_COL_SPLIT_INTERVAL = "splitInterval"; private static final String GPX_COL_SPLIT_INTERVAL = "splitInterval";
@ -98,6 +99,7 @@ public class GPXDatabase {
GPX_COL_WPT_POINTS + " int, " + GPX_COL_WPT_POINTS + " int, " +
GPX_COL_COLOR + " TEXT, " + GPX_COL_COLOR + " TEXT, " +
GPX_COL_FILE_LAST_MODIFIED_TIME + " long, " + GPX_COL_FILE_LAST_MODIFIED_TIME + " long, " +
GPX_COL_FILE_LAST_UPLOADED_TIME + " long, " +
GPX_COL_SPLIT_TYPE + " int, " + GPX_COL_SPLIT_TYPE + " int, " +
GPX_COL_SPLIT_INTERVAL + " double, " + GPX_COL_SPLIT_INTERVAL + " double, " +
GPX_COL_API_IMPORTED + " int, " + // 1 = true, 0 = false GPX_COL_API_IMPORTED + " int, " + // 1 = true, 0 = false
@ -133,6 +135,7 @@ public class GPXDatabase {
GPX_COL_WPT_POINTS + ", " + GPX_COL_WPT_POINTS + ", " +
GPX_COL_COLOR + ", " + GPX_COL_COLOR + ", " +
GPX_COL_FILE_LAST_MODIFIED_TIME + ", " + GPX_COL_FILE_LAST_MODIFIED_TIME + ", " +
GPX_COL_FILE_LAST_UPLOADED_TIME + ", " +
GPX_COL_SPLIT_TYPE + ", " + GPX_COL_SPLIT_TYPE + ", " +
GPX_COL_SPLIT_INTERVAL + ", " + GPX_COL_SPLIT_INTERVAL + ", " +
GPX_COL_API_IMPORTED + ", " + GPX_COL_API_IMPORTED + ", " +
@ -184,6 +187,7 @@ public class GPXDatabase {
private int splitType; private int splitType;
private double splitInterval; private double splitInterval;
private long fileLastModifiedTime; private long fileLastModifiedTime;
private long fileLastUploadedTime;
private boolean apiImported; private boolean apiImported;
private boolean showAsMarkers; private boolean showAsMarkers;
private boolean joinSegments; private boolean joinSegments;
@ -200,6 +204,11 @@ public class GPXDatabase {
this.color = color; this.color = color;
} }
public GpxDataItem(File file, long fileLastUploadedTime) {
this.file = file;
this.fileLastUploadedTime = fileLastUploadedTime;
}
public GpxDataItem(File file, @NonNull GPXFile gpxFile) { public GpxDataItem(File file, @NonNull GPXFile gpxFile) {
this.file = file; this.file = file;
readGpxParams(gpxFile); readGpxParams(gpxFile);
@ -263,6 +272,10 @@ public class GPXDatabase {
return fileLastModifiedTime; return fileLastModifiedTime;
} }
public long getFileLastUploadedTime() {
return fileLastUploadedTime;
}
public int getSplitType() { public int getSplitType() {
return splitType; return splitType;
} }
@ -441,10 +454,13 @@ public class GPXDatabase {
db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " + GPX_COL_SHOW_START_FINISH + " = ? " + db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " + GPX_COL_SHOW_START_FINISH + " = ? " +
"WHERE " + GPX_COL_SHOW_START_FINISH + " IS NULL", new Object[]{1}); "WHERE " + GPX_COL_SHOW_START_FINISH + " IS NULL", new Object[]{1});
} }
if (oldVersion < 12) {
db.execSQL("ALTER TABLE " + GPX_TABLE_NAME + " ADD " + GPX_COL_FILE_LAST_UPLOADED_TIME + " long");
}
db.execSQL("CREATE INDEX IF NOT EXISTS " + GPX_INDEX_NAME_DIR + " ON " + GPX_TABLE_NAME + " (" + GPX_COL_NAME + ", " + GPX_COL_DIR + ");"); db.execSQL("CREATE INDEX IF NOT EXISTS " + GPX_INDEX_NAME_DIR + " ON " + GPX_TABLE_NAME + " (" + GPX_COL_NAME + ", " + GPX_COL_DIR + ");");
} }
private boolean updateLastModifiedTime(GpxDataItem item) { private boolean updateLastModifiedTime(@NonNull GpxDataItem item) {
SQLiteConnection db = openConnection(false); SQLiteConnection db = openConnection(false);
if (db != null) { if (db != null) {
try { try {
@ -464,6 +480,25 @@ public class GPXDatabase {
return false; return false;
} }
public boolean updateLastUploadedTime(@NonNull GpxDataItem item, long fileLastUploadedTime) {
SQLiteConnection db = openConnection(false);
if (db != null) {
try {
String fileName = getFileName(item.file);
String fileDir = getFileDir(item.file);
db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " +
GPX_COL_FILE_LAST_UPLOADED_TIME + " = ? " +
" WHERE " + GPX_COL_NAME + " = ? AND " + GPX_COL_DIR + " = ?",
new Object[] { fileLastUploadedTime, fileName, fileDir });
item.fileLastUploadedTime = fileLastUploadedTime;
} finally {
db.close();
}
return true;
}
return false;
}
public boolean rename(@Nullable GpxDataItem item, File currentFile, File newFile) { public boolean rename(@Nullable GpxDataItem item, File currentFile, File newFile) {
SQLiteConnection db = openConnection(false); SQLiteConnection db = openConnection(false);
if (db != null){ if (db != null){
@ -721,11 +756,11 @@ public class GPXDatabase {
String gradientScaleType = item.gradientScaleType != null ? item.gradientScaleType.getTypeName() : null; String gradientScaleType = item.gradientScaleType != null ? item.gradientScaleType.getTypeName() : null;
if (a != null) { if (a != null) {
db.execSQL( db.execSQL(
"INSERT INTO " + GPX_TABLE_NAME + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "INSERT INTO " + GPX_TABLE_NAME + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
new Object[] {fileName, fileDir, a.totalDistance, a.totalTracks, a.startTime, a.endTime, new Object[] {fileName, fileDir, a.totalDistance, a.totalTracks, a.startTime, a.endTime,
a.timeSpan, a.timeMoving, a.totalDistanceMoving, a.diffElevationUp, a.diffElevationDown, a.timeSpan, a.timeMoving, a.totalDistanceMoving, a.diffElevationUp, a.diffElevationDown,
a.avgElevation, a.minElevation, a.maxElevation, a.maxSpeed, a.avgSpeed, a.points, a.wptPoints, a.avgElevation, a.minElevation, a.maxElevation, a.maxSpeed, a.avgSpeed, a.points, a.wptPoints,
color, item.file.lastModified(), item.splitType, item.splitInterval, item.apiImported ? 1 : 0, color, item.file.lastModified(), item.fileLastUploadedTime, item.splitType, item.splitInterval, item.apiImported ? 1 : 0,
Algorithms.encodeStringSet(item.analysis.wptCategoryNames), item.showAsMarkers ? 1 : 0, Algorithms.encodeStringSet(item.analysis.wptCategoryNames), item.showAsMarkers ? 1 : 0,
item.joinSegments ? 1 : 0, item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width, item.joinSegments ? 1 : 0, item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
item.gradientSpeedPalette, item.gradientAltitudePalette, item.gradientSlopePalette, gradientScaleType}); item.gradientSpeedPalette, item.gradientAltitudePalette, item.gradientSlopePalette, gradientScaleType});
@ -735,6 +770,7 @@ public class GPXDatabase {
GPX_COL_DIR + ", " + GPX_COL_DIR + ", " +
GPX_COL_COLOR + ", " + GPX_COL_COLOR + ", " +
GPX_COL_FILE_LAST_MODIFIED_TIME + ", " + GPX_COL_FILE_LAST_MODIFIED_TIME + ", " +
GPX_COL_FILE_LAST_UPLOADED_TIME + ", " +
GPX_COL_SPLIT_TYPE + ", " + GPX_COL_SPLIT_TYPE + ", " +
GPX_COL_SPLIT_INTERVAL + ", " + GPX_COL_SPLIT_INTERVAL + ", " +
GPX_COL_API_IMPORTED + ", " + GPX_COL_API_IMPORTED + ", " +
@ -747,8 +783,8 @@ public class GPXDatabase {
GPX_COL_GRADIENT_ALTITUDE_COLOR + ", " + GPX_COL_GRADIENT_ALTITUDE_COLOR + ", " +
GPX_COL_GRADIENT_SLOPE_COLOR + ", " + GPX_COL_GRADIENT_SLOPE_COLOR + ", " +
GPX_COL_GRADIENT_SCALE_TYPE + GPX_COL_GRADIENT_SCALE_TYPE +
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
new Object[] {fileName, fileDir, color, 0, item.splitType, item.splitInterval, new Object[] {fileName, fileDir, color, 0, item.fileLastUploadedTime, item.splitType, item.splitInterval,
item.apiImported ? 1 : 0, item.showAsMarkers ? 1 : 0, item.joinSegments ? 1 : 0, item.apiImported ? 1 : 0, item.showAsMarkers ? 1 : 0, item.joinSegments ? 1 : 0,
item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width, item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
Algorithms.gradientPaletteToString(item.gradientSpeedPalette), Algorithms.gradientPaletteToString(item.gradientSpeedPalette),
@ -828,19 +864,20 @@ public class GPXDatabase {
int wptPoints = (int)query.getInt(17); int wptPoints = (int)query.getInt(17);
String color = query.getString(18); String color = query.getString(18);
long fileLastModifiedTime = query.getLong(19); long fileLastModifiedTime = query.getLong(19);
int splitType = (int)query.getInt(20); long fileLastUploadedTime = query.getLong(20);
double splitInterval = query.getDouble(21); int splitType = (int)query.getInt(21);
boolean apiImported = query.getInt(22) == 1; double splitInterval = query.getDouble(22);
String wptCategoryNames = query.getString(23); boolean apiImported = query.getInt(23) == 1;
boolean showAsMarkers = query.getInt(24) == 1; String wptCategoryNames = query.getString(24);
boolean joinSegments = query.getInt(25) == 1; boolean showAsMarkers = query.getInt(25) == 1;
boolean showArrows = query.getInt(26) == 1; boolean joinSegments = query.getInt(26) == 1;
boolean showStartFinish = query.getInt(27) == 1; boolean showArrows = query.getInt(27) == 1;
String width = query.getString(28); boolean showStartFinish = query.getInt(28) == 1;
String gradientSpeedPalette = query.getString(29); String width = query.getString(29);
String gradientAltitudePalette = query.getString(30); String gradientSpeedPalette = query.getString(30);
String gradientSlopePalette = query.getString(31); String gradientAltitudePalette = query.getString(31);
String gradientScaleType = query.getString(32); String gradientSlopePalette = query.getString(32);
String gradientScaleType = query.getString(33);
GPXTrackAnalysis a = new GPXTrackAnalysis(); GPXTrackAnalysis a = new GPXTrackAnalysis();
a.totalDistance = totalDistance; a.totalDistance = totalDistance;
@ -873,6 +910,7 @@ public class GPXDatabase {
GpxDataItem item = new GpxDataItem(new File(dir, fileName), a); GpxDataItem item = new GpxDataItem(new File(dir, fileName), a);
item.color = parseColor(color); item.color = parseColor(color);
item.fileLastModifiedTime = fileLastModifiedTime; item.fileLastModifiedTime = fileLastModifiedTime;
item.fileLastUploadedTime = fileLastUploadedTime;
item.splitType = splitType; item.splitType = splitType;
item.splitInterval = splitInterval; item.splitInterval = splitInterval;
item.apiImported = apiImported; item.apiImported = apiImported;

View file

@ -78,6 +78,12 @@ public class GpxDbHelper {
return res; return res;
} }
public boolean updateLastUploadedTime(GpxDataItem item, long fileLastUploadedTime) {
boolean res = db.updateLastUploadedTime(item, fileLastUploadedTime);
putToCache(item);
return res;
}
public boolean updateGradientScalePalette(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int[] palette) { public boolean updateGradientScalePalette(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int[] palette) {
boolean res = db.updateGradientScaleColor(item, gradientScaleType, palette); boolean res = db.updateGradientScaleColor(item, gradientScaleType, palette);
putToCache(item); putToCache(item);

View file

@ -52,6 +52,7 @@ import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.activities.actions.OsmAndDialogs; import net.osmand.plus.activities.actions.OsmAndDialogs;
import net.osmand.plus.api.SQLiteAPI; import net.osmand.plus.api.SQLiteAPI;
import net.osmand.plus.api.SQLiteAPIImpl; import net.osmand.plus.api.SQLiteAPIImpl;
import net.osmand.plus.backup.BackupHelper;
import net.osmand.plus.base.MapViewTrackingUtilities; import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadIndexesThread;
import net.osmand.plus.download.DownloadService; import net.osmand.plus.download.DownloadService;
@ -169,6 +170,7 @@ public class OsmandApplication extends MultiDexApplication {
MeasurementEditingContext measurementEditingContext; MeasurementEditingContext measurementEditingContext;
OnlineRoutingHelper onlineRoutingHelper; OnlineRoutingHelper onlineRoutingHelper;
ItineraryHelper itineraryHelper; ItineraryHelper itineraryHelper;
BackupHelper backupHelper;
private Map<String, Builder> customRoutingConfigs = new ConcurrentHashMap<>(); private Map<String, Builder> customRoutingConfigs = new ConcurrentHashMap<>();
private File externalStorageDirectory; private File externalStorageDirectory;
@ -474,6 +476,10 @@ public class OsmandApplication extends MultiDexApplication {
return itineraryHelper; return itineraryHelper;
} }
public BackupHelper getBackupHelper() {
return backupHelper;
}
public TransportRoutingHelper getTransportRoutingHelper() { public TransportRoutingHelper getTransportRoutingHelper() {
return transportRoutingHelper; return transportRoutingHelper;
} }

View file

@ -5,6 +5,7 @@ import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.res.Resources;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -204,7 +205,8 @@ public class ProgressImplementation implements IProgress {
work = -1; work = -1;
progress = 0; progress = 0;
if (taskName != null) { if (taskName != null) {
message = context.getResources().getString(R.string.finished_task) +" : "+ taskName; //$NON-NLS-1$ Resources resources = context.getResources();
message = resources.getString(R.string.ltr_or_rtl_combine_via_colon, resources.getString(R.string.finished_task), taskName);
mViewUpdateHandler.sendEmptyMessage(HANDLER_START_TASK); mViewUpdateHandler.sendEmptyMessage(HANDLER_START_TASK);
} }
} }

View file

@ -0,0 +1,581 @@
package net.osmand.plus.backup;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.provider.Settings;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnFilesDownloadCallback;
import net.osmand.AndroidNetworkUtils.OnFilesUploadCallback;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.AndroidNetworkUtils.OnSendRequestsListener;
import net.osmand.AndroidNetworkUtils.Request;
import net.osmand.AndroidNetworkUtils.RequestResponse;
import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.plus.FavouritesDbHelper;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxDbHelper;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BackupHelper {
private final OsmandApplication app;
private final OsmandSettings settings;
private final FavouritesDbHelper favouritesHelper;
private final GpxDbHelper gpxHelper;
private static final String SERVER_URL = "https://osmand.net";
private static final String USER_REGISTER_URL = SERVER_URL + "/userdata/user-register";
private static final String DEVICE_REGISTER_URL = SERVER_URL + "/userdata/device-register";
private static final String UPLOAD_FILE_URL = SERVER_URL + "/userdata/upload-file";
private static final String LIST_FILES_URL = SERVER_URL + "/userdata/list-files";
private static final String DOWNLOAD_FILE_URL = SERVER_URL + "/userdata/download-file";
private static final String DELETE_FILE_URL = SERVER_URL + "/userdata/delete-file";
public final static int STATUS_SUCCESS = 0;
public final static int STATUS_PARSE_JSON_ERROR = 1;
public final static int STATUS_EMPTY_RESPONSE_ERROR = 2;
public final static int STATUS_SERVER_ERROR = 3;
public interface OnResultListener {
void onResult(int status, @Nullable String message, @Nullable JSONObject json);
}
public interface OnRegisterUserListener {
void onRegisterUser(int status, @Nullable String message);
}
public interface OnRegisterDeviceListener {
void onRegisterDevice(int status, @Nullable String message);
}
public interface OnDownloadFileListListener {
void onDownloadFileList(int status, @Nullable String message, @NonNull List<UserFile> userFiles);
}
public interface OnCollectLocalFilesListener {
void onFileCollected(@NonNull GpxFileInfo fileInfo);
void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos);
}
public interface OnGenerateBackupInfoListener {
void onBackupInfoGenerated(@Nullable BackupInfo backupInfo, @Nullable String error);
}
public interface OnUploadFilesListener {
void onFileUploadProgress(@NonNull File file, int progress);
void onFilesUploadDone(@NonNull Map<File, String> errors);
}
public interface OnDeleteFilesListener {
void onFileDeleteProgress(@NonNull UserFile file);
void onFilesDeleteDone(@NonNull Map<UserFile, String> errors);
}
public interface OnDownloadFileListener {
void onFileDownloadProgress(@NonNull UserFile userFile, int progress);
@WorkerThread
void onFileDownloadedAsync(@NonNull File file);
void onFilesDownloadDone(@NonNull Map<File, String> errors);
}
public static class BackupInfo {
public List<UserFile> filesToDownload = new ArrayList<>();
public List<GpxFileInfo> filesToUpload = new ArrayList<>();
public List<UserFile> filesToDelete = new ArrayList<>();
public List<Pair<GpxFileInfo, UserFile>> filesToMerge = new ArrayList<>();
}
public BackupHelper(@NonNull OsmandApplication app) {
this.app = app;
this.settings = app.getSettings();
this.favouritesHelper = app.getFavorites();
this.gpxHelper = app.getGpxDbHelper();
}
@SuppressLint("HardwareIds")
private String getAndroidId() {
try {
return Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID);
} catch (Exception e) {
return null;
}
}
public static boolean isTokenValid(@NonNull String token) {
return token.matches("[0-9]+");
}
public boolean hasOsmLiveUpdates() {
return InAppPurchaseHelper.isSubscribedToLiveUpdates(app);
}
@Nullable
public String getOrderId() {
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
InAppSubscription purchasedSubscription = purchaseHelper.getAnyPurchasedSubscription();
return purchasedSubscription != null ? purchasedSubscription.getOrderId() : null;
}
public String getDeviceId() {
return settings.BACKUP_DEVICE_ID.get();
}
public String getAccessToken() {
return settings.BACKUP_ACCESS_TOKEN.get();
}
public String getEmail() {
return settings.BACKUP_USER_EMAIL.get();
}
public boolean isRegistered() {
return !Algorithms.isEmpty(getDeviceId()) && !Algorithms.isEmpty(getAccessToken());
}
private void checkRegistered() throws UserNotRegisteredException {
if (Algorithms.isEmpty(getDeviceId()) || Algorithms.isEmpty(getAccessToken())) {
throw new UserNotRegisteredException();
}
}
public void registerUser(@NonNull String email, @Nullable final OnRegisterUserListener listener) {
Map<String, String> params = new HashMap<>();
params.put("email", email);
params.put("orderid", getOrderId());
params.put("deviceid", app.getUserAndroidId());
AndroidNetworkUtils.sendRequestAsync(app, USER_REGISTER_URL, params, "Register user", true, true, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
int status;
String message;
if (!Algorithms.isEmpty(resultJson)) {
try {
JSONObject result = new JSONObject(resultJson);
String statusStr = result.getString("status");
if (statusStr.equals("ok")) {
message = "You have been registered successfully. Please check for email with activation code.";
status = STATUS_SUCCESS;
} else {
message = "User registration error: " + statusStr;
status = STATUS_SERVER_ERROR;
}
} catch (JSONException e) {
message = "User registration error: json parsing";
status = STATUS_PARSE_JSON_ERROR;
}
} else {
message = "User registration error: empty response";
status = STATUS_EMPTY_RESPONSE_ERROR;
}
if (listener != null) {
listener.onRegisterUser(status, message);
}
}
});
}
public void registerDevice(String token, @Nullable final OnRegisterDeviceListener listener) {
Map<String, String> params = new HashMap<>();
params.put("email", getEmail());
String orderId = getOrderId();
if (orderId != null) {
params.put("orderid", orderId);
}
String androidId = getAndroidId();
if (!Algorithms.isEmpty(androidId)) {
params.put("deviceid", androidId);
}
params.put("token", token);
AndroidNetworkUtils.sendRequestAsync(app, DEVICE_REGISTER_URL, params, "Register device", true, true, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
int status;
String message;
if (!Algorithms.isEmpty(resultJson)) {
try {
JSONObject result = new JSONObject(resultJson);
settings.BACKUP_DEVICE_ID.set(result.getString("id"));
settings.BACKUP_USER_ID.set(result.getString("userid"));
settings.BACKUP_NATIVE_DEVICE_ID.set(result.getString("deviceid"));
settings.BACKUP_ACCESS_TOKEN.set(result.getString("accesstoken"));
settings.BACKUP_ACCESS_TOKEN_UPDATE_TIME.set(result.getString("udpatetime"));
status = STATUS_SUCCESS;
message = "Device have been registered successfully";
} catch (JSONException e) {
message = "Device registration error: json parsing";
status = STATUS_PARSE_JSON_ERROR;
}
} else {
message = "Device registration error: empty response";
status = STATUS_EMPTY_RESPONSE_ERROR;
}
if (listener != null) {
listener.onRegisterDevice(status, message);
}
}
});
}
public void uploadFiles(@NonNull List<GpxFileInfo> gpxFiles, @Nullable final OnUploadFilesListener listener) throws UserNotRegisteredException {
checkRegistered();
Map<String, String> params = new HashMap<>();
params.put("deviceid", getDeviceId());
params.put("accessToken", getAccessToken());
Map<String, String> headers = new HashMap<>();
headers.put("Accept-Encoding", "deflate, gzip");
final Map<File, GpxFileInfo> gpxInfos = new HashMap<>();
for (GpxFileInfo gpxFile : gpxFiles) {
gpxInfos.put(gpxFile.file, gpxFile);
}
final File favoritesFile = favouritesHelper.getExternalFile();
AndroidNetworkUtils.uploadFilesAsync(UPLOAD_FILE_URL, new ArrayList<>(gpxInfos.keySet()), true, params, headers, new OnFilesUploadCallback() {
@Nullable
@Override
public Map<String, String> getAdditionalParams(@NonNull File file) {
Map<String, String> additionaParams = new HashMap<>();
GpxFileInfo gpxFileInfo = gpxInfos.get(file);
if (gpxFileInfo != null) {
additionaParams.put("name", gpxFileInfo.getFileName(true));
additionaParams.put("type", Algorithms.getFileExtension(file));
gpxFileInfo.uploadTime = System.currentTimeMillis();
if (file.equals(favoritesFile)) {
favouritesHelper.setLastUploadedTime(gpxFileInfo.uploadTime);
} else {
GpxDataItem gpxItem = gpxHelper.getItem(file);
if (gpxItem != null) {
gpxHelper.updateLastUploadedTime(gpxItem, gpxFileInfo.uploadTime);
}
}
additionaParams.put("clienttime", String.valueOf(gpxFileInfo.uploadTime));
}
return additionaParams;
}
@Override
public void onFileUploadProgress(@NonNull File file, int progress) {
if (listener != null) {
listener.onFileUploadProgress(file, progress);
}
}
@Override
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
if (errors.isEmpty()) {
settings.BACKUP_LAST_UPLOADED_TIME.set(System.currentTimeMillis() + 1);
}
if (listener != null) {
listener.onFilesUploadDone(errors);
}
}
});
}
public void deleteFiles(@NonNull List<UserFile> userFiles, @Nullable final OnDeleteFilesListener listener) throws UserNotRegisteredException {
checkRegistered();
Map<String, String> commonParameters = new HashMap<>();
commonParameters.put("deviceid", getDeviceId());
commonParameters.put("accessToken", getAccessToken());
final List<Request> requests = new ArrayList<>();
final Map<Request, UserFile> filesMap = new HashMap<>();
for (UserFile userFile : userFiles) {
Map<String, String> parameters = new HashMap<>(commonParameters);
parameters.put("name", userFile.getName());
parameters.put("type", userFile.getType());
Request r = new Request(DELETE_FILE_URL, parameters, null, false, true);
requests.add(r);
filesMap.put(r, userFile);
}
AndroidNetworkUtils.sendRequestsAsync(null, requests, new OnSendRequestsListener() {
@Override
public void onRequestSent(@NonNull RequestResponse response) {
if (listener != null) {
UserFile userFile = filesMap.get(response.getRequest());
if (userFile != null) {
listener.onFileDeleteProgress(userFile);
}
}
}
@Override
public void onRequestsSent(@NonNull List<RequestResponse> results) {
if (listener != null) {
Map<UserFile, String> errors = new HashMap<>();
for (RequestResponse response : results) {
UserFile userFile = filesMap.get(response.getRequest());
if (userFile != null) {
String responseStr = response.getResponse();
boolean success;
try {
JSONObject json = new JSONObject(responseStr);
String status = json.getString("status");
success = status.equalsIgnoreCase("ok");
} catch (JSONException e) {
success = false;
}
if (!success) {
errors.put(userFile, responseStr);
}
}
}
listener.onFilesDeleteDone(errors);
}
}
});
}
public void downloadFileList(@Nullable final OnDownloadFileListListener listener) throws UserNotRegisteredException {
checkRegistered();
Map<String, String> params = new HashMap<>();
params.put("deviceid", getDeviceId());
params.put("accessToken", getAccessToken());
AndroidNetworkUtils.sendRequestAsync(app, LIST_FILES_URL, params, "Download file list", true, false, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
int status;
String message;
List<UserFile> userFiles = new ArrayList<>();
if (!Algorithms.isEmpty(resultJson)) {
try {
JSONObject result = new JSONObject(resultJson);
String totalZipSize = result.getString("totalZipSize");
String totalFiles = result.getString("totalFiles");
String totalFileVersions = result.getString("totalFileVersions");
JSONArray files = result.getJSONArray("uniqueFiles");
for (int i = 0; i < files.length(); i++) {
userFiles.add(new UserFile(files.getJSONObject(i)));
}
status = STATUS_SUCCESS;
message = "Total files: " + totalFiles + "\n" +
"Total zip size: " + AndroidUtils.formatSize(app, Long.parseLong(totalZipSize)) + "\n" +
"Total file versions: " + totalFileVersions;
} catch (JSONException | ParseException e) {
status = STATUS_PARSE_JSON_ERROR;
message = "Download file list error: json parsing";
}
} else {
status = STATUS_EMPTY_RESPONSE_ERROR;
message = "Download file list error: empty response";
}
if (listener != null) {
listener.onDownloadFileList(status, message, userFiles);
}
}
});
}
public void downloadFiles(@NonNull final Map<File, UserFile> filesMap, @Nullable final OnDownloadFileListener listener) throws UserNotRegisteredException {
checkRegistered();
Map<String, String> params = new HashMap<>();
params.put("deviceid", getDeviceId());
params.put("accessToken", getAccessToken());
AndroidNetworkUtils.downloadFilesAsync(DOWNLOAD_FILE_URL,
new ArrayList<>(filesMap.keySet()), params, new OnFilesDownloadCallback() {
@Nullable
@Override
public Map<String, String> getAdditionalParams(@NonNull File file) {
UserFile userFile = filesMap.get(file);
Map<String, String> additionaParams = new HashMap<>();
additionaParams.put("name", userFile.getName());
additionaParams.put("type", userFile.getType());
return additionaParams;
}
@Override
public void onFileDownloadProgress(@NonNull File file, int percent) {
if (listener != null) {
listener.onFileDownloadProgress(filesMap.get(file), percent);
}
}
@Override
public void onFileDownloadedAsync(@NonNull File file) {
if (listener != null) {
listener.onFileDownloadedAsync(file);
}
}
@Override
public void onFilesDownloadDone(@NonNull Map<File, String> errors) {
if (listener != null) {
listener.onFilesDownloadDone(errors);
}
}
});
}
@SuppressLint("StaticFieldLeak")
public void collectLocalFiles(@Nullable final OnCollectLocalFilesListener listener) {
AsyncTask<Void, GpxFileInfo, List<GpxFileInfo>> task = new AsyncTask<Void, GpxFileInfo, List<GpxFileInfo>>() {
private final OnCollectLocalFilesListener internalListener = new OnCollectLocalFilesListener() {
@Override
public void onFileCollected(@NonNull GpxFileInfo fileInfo) {
publishProgress(fileInfo);
}
@Override
public void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos) {
}
};
private void loadGPXData(@NonNull File mapPath, @NonNull List<GpxFileInfo> result,
@Nullable OnCollectLocalFilesListener listener) {
if (mapPath.canRead()) {
loadGPXFolder(mapPath, result, "", listener);
}
}
private void loadGPXFolder(@NonNull File mapPath, @NonNull List<GpxFileInfo> result,
@NonNull String gpxSubfolder, @Nullable OnCollectLocalFilesListener listener) {
File[] listFiles = mapPath.listFiles();
if (listFiles != null) {
for (File gpxFile : listFiles) {
if (gpxFile.isDirectory()) {
String sub = gpxSubfolder.length() == 0 ? gpxFile.getName() : gpxSubfolder + "/"
+ gpxFile.getName();
loadGPXFolder(gpxFile, result, sub, listener);
} else if (gpxFile.isFile() && gpxFile.getName().toLowerCase().endsWith(IndexConstants.GPX_FILE_EXT)) {
GpxFileInfo info = new GpxFileInfo();
info.subfolder = gpxSubfolder;
info.file = gpxFile;
GpxDataItem gpxItem = gpxHelper.getItem(gpxFile);
if (gpxItem != null) {
info.uploadTime = gpxItem.getFileLastUploadedTime();
}
result.add(info);
if (listener != null) {
listener.onFileCollected(info);
}
}
}
}
}
@Override
protected List<GpxFileInfo> doInBackground(Void... voids) {
List<GpxFileInfo> result = new ArrayList<>();
GpxFileInfo favInfo = new GpxFileInfo();
favInfo.subfolder = "";
favInfo.file = favouritesHelper.getExternalFile();
favInfo.uploadTime = favouritesHelper.getLastUploadedTime();
result.add(favInfo);
if (listener != null) {
listener.onFileCollected(favInfo);
}
loadGPXData(app.getAppPath(IndexConstants.GPX_INDEX_DIR), result, internalListener);
return result;
}
@Override
protected void onProgressUpdate(GpxFileInfo... fileInfos) {
if (listener != null) {
listener.onFileCollected(fileInfos[0]);
}
}
@Override
protected void onPostExecute(List<GpxFileInfo> fileInfos) {
if (listener != null) {
listener.onFilesCollected(fileInfos);
}
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@SuppressLint("StaticFieldLeak")
public void generateBackupInfo(@NonNull final List<GpxFileInfo> localFiles, @NonNull final List<UserFile> remoteFiles,
@Nullable final OnGenerateBackupInfoListener listener) {
final long backupLastUploadedTime = settings.BACKUP_LAST_UPLOADED_TIME.get();
AsyncTask<Void, Void, BackupInfo> task = new AsyncTask<Void, Void, BackupInfo>() {
@Override
protected BackupInfo doInBackground(Void... voids) {
BackupInfo info = new BackupInfo();
for (UserFile remoteFile : remoteFiles) {
boolean hasLocalFile = false;
for (GpxFileInfo localFile : localFiles) {
if (remoteFile.getName().equals(localFile.getFileName(true))) {
hasLocalFile = true;
long remoteUploadTime = remoteFile.getClienttimems();
long localUploadTime = localFile.uploadTime;
long localModifiedTime = localFile.file.lastModified();
if (remoteUploadTime == localUploadTime) {
if (localUploadTime < localModifiedTime) {
info.filesToUpload.add(localFile);
}
} else {
info.filesToMerge.add(new Pair<>(localFile, remoteFile));
}
break;
}
}
if (!hasLocalFile) {
if (backupLastUploadedTime > 0 && backupLastUploadedTime >= remoteFile.getClienttimems()) {
info.filesToDelete.add(remoteFile);
} else {
info.filesToDownload.add(remoteFile);
}
}
}
for (GpxFileInfo localFile : localFiles) {
boolean hasRemoteFile = false;
for (UserFile remoteFile : remoteFiles) {
if (localFile.getFileName(true).equals(remoteFile.getName())) {
hasRemoteFile = true;
break;
}
}
if (!hasRemoteFile) {
info.filesToUpload.add(localFile);
}
}
return info;
}
@Override
protected void onPostExecute(BackupInfo backupInfo) {
if (listener != null) {
listener.onBackupInfoGenerated(backupInfo, null);
}
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View file

@ -0,0 +1,335 @@
package net.osmand.plus.backup;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxDbHelper;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.backup.BackupHelper.BackupInfo;
import net.osmand.plus.backup.BackupHelper.OnDeleteFilesListener;
import net.osmand.plus.backup.BackupHelper.OnDownloadFileListener;
import net.osmand.plus.backup.BackupHelper.OnUploadFilesListener;
import net.osmand.plus.importfiles.FavoritesImportTask;
import net.osmand.util.Algorithms;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import static net.osmand.IndexConstants.GPX_INDEX_DIR;
import static net.osmand.IndexConstants.TEMP_DIR;
public class BackupTask {
private final OsmandApplication app;
private final BackupHelper backupHelper;
private final OnBackupListener listener;
private final WeakReference<Context> contextRef;
private ProgressImplementation progress;
private final BackupInfo backupInfo;
private Map<File, String> uploadErrors;
private Map<File, String> downloadErrors;
private Map<UserFile, String> deleteErrors;
private String error;
private final TaskType[] backupTasks = {TaskType.UPLOAD_FILES, TaskType.DELETE_FILES};
private final TaskType[] restoreTasks = {TaskType.DOWNLOAD_FILES};
private Stack<TaskType> runningTasks = new Stack<>();
private enum TaskType {
UPLOAD_FILES,
DOWNLOAD_FILES,
DELETE_FILES
}
public interface OnBackupListener {
void onBackupDone(@Nullable Map<File, String> uploadErrors,
@Nullable Map<File, String> downloadErrors,
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error);
}
public BackupTask(@NonNull BackupInfo backupInfo, @NonNull Context context, @Nullable OnBackupListener listener) {
this.contextRef = new WeakReference<>(context);
this.app = (OsmandApplication) context.getApplicationContext();
this.backupHelper = app.getBackupHelper();
this.backupInfo = backupInfo;
this.listener = listener;
}
public BackupInfo getBackupInfo() {
return backupInfo;
}
public Map<File, String> getUploadErrors() {
return uploadErrors;
}
public Map<File, String> getDownloadErrors() {
return downloadErrors;
}
public Map<UserFile, String> getDeleteErrors() {
return deleteErrors;
}
public String getError() {
return error;
}
public boolean runBackup() {
if (!runningTasks.empty()) {
return false;
}
initBackupTasks();
return runTasks();
}
public boolean runRestore() {
if (!runningTasks.empty()) {
return false;
}
initRestoreTasks();
return runTasks();
}
private void initBackupTasks() {
initData();
Stack<TaskType> tasks = new Stack<>();
for (int i = backupTasks.length - 1; i >= 0; i--) {
tasks.push(backupTasks[i]);
}
this.runningTasks = tasks;
onTasksInit();
}
private void initRestoreTasks() {
initData();
Stack<TaskType> tasks = new Stack<>();
for (int i = restoreTasks.length - 1; i >= 0; i--) {
tasks.push(restoreTasks[i]);
}
this.runningTasks = tasks;
onTasksInit();
}
private void initData() {
uploadErrors = null;
downloadErrors = null;
deleteErrors = null;
error = null;
}
private boolean runTasks() {
if (runningTasks.empty()) {
return false;
} else {
TaskType taskType = runningTasks.pop();
runTask(taskType);
return true;
}
}
private void runTask(@NonNull TaskType taskType) {
switch (taskType) {
case UPLOAD_FILES:
doUploadFiles();
break;
case DOWNLOAD_FILES:
doDownloadFiles();
break;
case DELETE_FILES:
doDeleteFiles();
break;
}
}
private void onTaskFinished(@NonNull TaskType taskType) {
if (!runTasks()) {
onTasksDone();
}
}
private void doUploadFiles() {
if (Algorithms.isEmpty(backupInfo.filesToUpload)) {
onTaskFinished(TaskType.UPLOAD_FILES);
return;
}
onTaskProgressUpdate("Upload files...");
try {
backupHelper.uploadFiles(backupInfo.filesToUpload, new OnUploadFilesListener() {
@Override
public void onFileUploadProgress(@NonNull File file, int progress) {
if (progress == 0) {
onTaskProgressUpdate(file.getName(), (int) (file.length() / 1024));
} else {
onTaskProgressUpdate(progress);
}
}
@Override
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
uploadErrors = errors;
onTaskFinished(TaskType.UPLOAD_FILES);
}
});
} catch (UserNotRegisteredException e) {
onError("User is not registered");
}
}
private void doDownloadFiles() {
if (Algorithms.isEmpty(backupInfo.filesToDownload)) {
onTaskFinished(TaskType.DOWNLOAD_FILES);
return;
}
onTaskProgressUpdate("Download files...");
File favoritesFile = app.getFavorites().getExternalFile();
String favoritesFileName = favoritesFile.getName();
File tempFavoritesFile = null;
final Map<File, UserFile> filesMap = new HashMap<>();
for (UserFile userFile : backupInfo.filesToDownload) {
File file;
String fileName = userFile.getName();
if (favoritesFileName.equals(fileName)) {
file = new File(app.getAppPath(TEMP_DIR), fileName);
tempFavoritesFile = file;
} else {
file = new File(app.getAppPath(GPX_INDEX_DIR), fileName);
}
filesMap.put(file, userFile);
}
final File finalTempFavoritesFile = tempFavoritesFile;
try {
backupHelper.downloadFiles(filesMap, new OnDownloadFileListener() {
@Override
public void onFileDownloadProgress(@NonNull UserFile userFile, int progress) {
if (progress == 0) {
onTaskProgressUpdate(new File(userFile.getName()).getName(), userFile.getFilesize() / 1024);
} else {
onTaskProgressUpdate(progress);
}
}
@Override
public void onFileDownloadedAsync(@NonNull File file) {
UserFile userFile = filesMap.get(file);
long userFileTime = userFile.getClienttimems();
if (file.equals(finalTempFavoritesFile)) {
GPXFile gpxFile = GPXUtilities.loadGPXFile(finalTempFavoritesFile);
FavoritesImportTask.mergeFavorites(app, gpxFile, "", false);
finalTempFavoritesFile.delete();
app.getFavorites().getExternalFile().setLastModified(userFileTime);
} else {
file.setLastModified(userFileTime);
GpxDataItem item = new GpxDataItem(file, userFileTime);
app.getGpxDbHelper().add(item);
}
}
@Override
public void onFilesDownloadDone(@NonNull Map<File, String> errors) {
downloadErrors = errors;
onTaskFinished(TaskType.DOWNLOAD_FILES);
}
});
} catch (UserNotRegisteredException e) {
onError("User is not registered");
}
}
private void doDeleteFiles() {
if (Algorithms.isEmpty(backupInfo.filesToDelete)) {
onTaskFinished(TaskType.DELETE_FILES);
return;
}
onTaskProgressUpdate("Delete files...");
try {
backupHelper.deleteFiles(backupInfo.filesToDelete, new OnDeleteFilesListener() {
@Override
public void onFileDeleteProgress(@NonNull UserFile userFile) {
onTaskProgressUpdate(userFile.getName());
}
@Override
public void onFilesDeleteDone(@NonNull Map<UserFile, String> errors) {
deleteErrors = errors;
onTaskFinished(TaskType.DELETE_FILES);
}
});
} catch (UserNotRegisteredException e) {
onError("User is not registered");
}
}
private void onTasksInit() {
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
progress = ProgressImplementation.createProgressDialog(ctx,
"Backup data", "Initializing...", ProgressDialog.STYLE_HORIZONTAL);
}
}
private void onTaskProgressUpdate(Object... objects) {
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
if (objects != null) {
if (objects.length == 1) {
if (objects[0] instanceof String) {
progress.startTask((String) objects[0], -1);
} else if (objects[0] instanceof Integer) {
int progressValue = (Integer) objects[0];
if (progressValue < Integer.MAX_VALUE) {
progress.progress(progressValue);
} else {
progress.finishTask();
}
}
} else if (objects.length == 2) {
progress.startTask((String) objects[0], (Integer) objects[1]);
}
}
}
}
private void onError(@NonNull String message) {
this.error = message;
runningTasks.clear();
onTasksDone();
}
private void onTasksDone() {
if (listener != null) {
listener.onBackupDone(uploadErrors, downloadErrors, deleteErrors, error);
}
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
progress.finishTask();
app.runInUIThread(new Runnable() {
@Override
public void run() {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
}, 300);
}
}
}

View file

@ -0,0 +1,65 @@
package net.osmand.plus.backup;
import net.osmand.util.Algorithms;
import java.io.File;
public class GpxFileInfo {
public File file;
public String subfolder;
public long uploadTime = 0;
private String name = null;
private int sz = -1;
private String fileName = null;
public String getName() {
if (name == null) {
name = formatName(file.getName());
}
return name;
}
private String formatName(String name) {
int ext = name.lastIndexOf('.');
if (ext != -1) {
name = name.substring(0, ext);
}
return name.replace('_', ' ');
}
// Usage: AndroidUtils.formatSize(v.getContext(), getSize() * 1024l);
public int getSize() {
if (sz == -1) {
if (file == null) {
return -1;
}
sz = (int) ((file.length() + 512) >> 10);
}
return sz;
}
public long getFileDate() {
if (file == null) {
return 0;
}
return file.lastModified();
}
public String getFileName(boolean includeSubfolder) {
String result;
if (fileName != null) {
result = fileName;
} else {
if (file == null) {
result = "";
} else {
result = fileName = file.getName();
}
}
if (includeSubfolder && !Algorithms.isEmpty(subfolder)) {
result = subfolder + "/" + result;
}
return result;
}
}

View file

@ -0,0 +1,211 @@
package net.osmand.plus.backup;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.backup.BackupHelper.BackupInfo;
import net.osmand.plus.backup.BackupHelper.OnCollectLocalFilesListener;
import net.osmand.plus.backup.BackupHelper.OnDownloadFileListListener;
import net.osmand.plus.backup.BackupHelper.OnGenerateBackupInfoListener;
import net.osmand.util.Algorithms;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Stack;
public class PrepareBackupTask {
private final OsmandApplication app;
private final BackupHelper backupHelper;
private final OnPrepareBackupListener listener;
private final WeakReference<Context> contextRef;
private ProgressImplementation progress;
private BackupInfo result;
private List<UserFile> userFiles;
private List<GpxFileInfo> fileInfos;
private String error;
private Stack<TaskType> runningTasks = new Stack<>();
private enum TaskType {
COLLECT_LOCAL_FILES,
COLLECT_REMOTE_FILES,
GENERATE_BACKUP_INFO
}
public interface OnPrepareBackupListener {
void onBackupPrepared(@Nullable BackupInfo backupInfo, @Nullable String error);
}
public PrepareBackupTask(@NonNull Context context, @Nullable OnPrepareBackupListener listener) {
this.contextRef = new WeakReference<>(context);
this.app = (OsmandApplication) context.getApplicationContext();
this.backupHelper = app.getBackupHelper();
this.listener = listener;
}
public BackupInfo getResult() {
return result;
}
public String getError() {
return error;
}
public boolean prepare() {
if (!runningTasks.empty()) {
return false;
}
initTasks();
return runTasks();
}
private void initTasks() {
result = null;
userFiles = null;
fileInfos = null;
error = null;
Stack<TaskType> tasks = new Stack<>();
TaskType[] types = TaskType.values();
for (int i = types.length - 1; i >= 0; i--) {
tasks.push(types[i]);
}
this.runningTasks = tasks;
onTasksInit();
}
private boolean runTasks() {
if (runningTasks.empty()) {
return false;
} else {
TaskType taskType = runningTasks.pop();
runTask(taskType);
return true;
}
}
private void runTask(@NonNull TaskType taskType) {
switch (taskType) {
case COLLECT_LOCAL_FILES:
doCollectLocalFiles();
break;
case COLLECT_REMOTE_FILES:
doCollectRemoteFiles();
break;
case GENERATE_BACKUP_INFO:
doGenerateBackupInfo();
break;
}
}
private void onTaskFinished(@NonNull TaskType taskType) {
if (!runTasks()) {
onTasksDone();
}
}
private void doCollectLocalFiles() {
onTaskProgressUpdate("Collecting local info...");
backupHelper.collectLocalFiles(new OnCollectLocalFilesListener() {
@Override
public void onFileCollected(@NonNull GpxFileInfo fileInfo) {
}
@Override
public void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos) {
PrepareBackupTask.this.fileInfos = fileInfos;
onTaskFinished(TaskType.COLLECT_LOCAL_FILES);
}
});
}
private void doCollectRemoteFiles() {
onTaskProgressUpdate("Downloading remote info...");
try {
backupHelper.downloadFileList(new OnDownloadFileListListener() {
@Override
public void onDownloadFileList(int status, @Nullable String message, @NonNull List<UserFile> userFiles) {
if (status == BackupHelper.STATUS_SUCCESS) {
PrepareBackupTask.this.userFiles = userFiles;
} else {
onError(!Algorithms.isEmpty(message) ? message : "Download file list error: " + status);
}
onTaskFinished(TaskType.COLLECT_REMOTE_FILES);
}
});
} catch (UserNotRegisteredException e) {
onError("User is not registered");
}
}
private void doGenerateBackupInfo() {
if (fileInfos == null || userFiles == null) {
onTaskFinished(TaskType.GENERATE_BACKUP_INFO);
return;
}
onTaskProgressUpdate("Generating backup info...");
backupHelper.generateBackupInfo(fileInfos, userFiles, new OnGenerateBackupInfoListener() {
@Override
public void onBackupInfoGenerated(@Nullable BackupInfo backupInfo, @Nullable String error) {
if (Algorithms.isEmpty(error)) {
PrepareBackupTask.this.result = backupInfo;
} else {
onError(error);
}
onTaskFinished(TaskType.GENERATE_BACKUP_INFO);
}
});
}
private void onTasksInit() {
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx)) {
progress = ProgressImplementation.createProgressDialog(ctx,
"Prepare backup", "Initializing...", ProgressDialog.STYLE_HORIZONTAL);
}
}
private void onTaskProgressUpdate(String message) {
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
progress.startTask(message, -1);
}
}
private void onError(@NonNull String message) {
this.error = message;
runningTasks.clear();
onTasksDone();
}
private void onTasksDone() {
if (listener != null) {
listener.onBackupPrepared(result, error);
}
Context ctx = contextRef.get();
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
progress.finishTask();
app.runInUIThread(new Runnable() {
@Override
public void run() {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
}, 300);
}
}
}

View file

@ -0,0 +1,102 @@
package net.osmand.plus.backup;
import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
public class UserFile {
private int userid;
private long id;
private int deviceid;
private int filesize;
private String type;
private String name;
private Date updatetime;
private long updatetimems;
private Date clienttime;
private long clienttimems;
private int zipSize;
public UserFile(@NonNull JSONObject json) throws JSONException, ParseException {
if (json.has("userid")) {
userid = json.getInt("userid");
}
if (json.has("id")) {
id = json.getLong("id");
}
if (json.has("deviceid")) {
deviceid = json.getInt("deviceid");
}
if (json.has("filesize")) {
filesize = json.getInt("filesize");
}
if (json.has("type")) {
type = json.getString("type");
}
if (json.has("name")) {
name = json.getString("name");
}
if (json.has("updatetimems")) {
updatetimems = json.getLong("updatetimems");
updatetime = new Date(updatetimems);
}
if (json.has("clienttimems")) {
clienttimems = json.getLong("clienttimems");
clienttime = new Date(clienttimems);
}
if (json.has("zipSize")) {
zipSize = json.getInt("zipSize");
}
}
public int getUserid() {
return userid;
}
public long getId() {
return id;
}
public int getDeviceid() {
return deviceid;
}
public int getFilesize() {
return filesize;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
public Date getUpdatetime() {
return updatetime;
}
public long getUpdatetimems() {
return updatetimems;
}
public Date getClienttime() {
return clienttime;
}
public long getClienttimems() {
return clienttimems;
}
public int getZipSize() {
return zipSize;
}
}

View file

@ -0,0 +1,9 @@
package net.osmand.plus.backup;
public class UserNotRegisteredException extends Exception {
private static final long serialVersionUID = -8005954380280822845L;
public UserNotRegisteredException() {
super("User is not resistered");
}
}

View file

@ -94,9 +94,9 @@ public class MultipleSelectionBottomSheet extends SelectionBottomSheet {
} }
@Override @Override
protected void notifyUiInitialized() { protected void notifyUiCreated() {
onSelectedItemsChanged(); onSelectedItemsChanged();
super.notifyUiInitialized(); super.notifyUiCreated();
} }
private void onSelectedItemsChanged() { private void onSelectedItemsChanged() {

View file

@ -52,7 +52,7 @@ public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment
protected int activeColorRes; protected int activeColorRes;
protected int secondaryColorRes; protected int secondaryColorRes;
private OnUiInitializedAdapter onUiInitializedAdapter; private DialogStateListener dialogStateListener;
private OnApplySelectionListener onApplySelectionListener; private OnApplySelectionListener onApplySelectionListener;
protected List<SelectableItem> allItems = new ArrayList<>(); protected List<SelectableItem> allItems = new ArrayList<>();
@ -64,7 +64,7 @@ public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View mainView = super.onCreateView(inflater, parent, savedInstanceState); View mainView = super.onCreateView(inflater, parent, savedInstanceState);
createSelectionListIfPossible(); createSelectionListIfPossible();
notifyUiInitialized(); notifyUiCreated();
return mainView; return mainView;
} }
@ -153,8 +153,8 @@ public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment
} }
} }
public void setOnUiInitializedAdapter(OnUiInitializedAdapter onUiInitializedAdapter) { public void setDialogStateListener(DialogStateListener dialogStateListener) {
this.onUiInitializedAdapter = onUiInitializedAdapter; this.dialogStateListener = dialogStateListener;
} }
public void setOnApplySelectionListener(OnApplySelectionListener onApplySelectionListener) { public void setOnApplySelectionListener(OnApplySelectionListener onApplySelectionListener) {
@ -194,9 +194,9 @@ public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment
protected abstract boolean shouldShowDivider(); protected abstract boolean shouldShowDivider();
protected void notifyUiInitialized() { protected void notifyUiCreated() {
if (onUiInitializedAdapter != null) { if (dialogStateListener != null) {
onUiInitializedAdapter.onUiInitialized(); dialogStateListener.onDialogCreated();
} }
} }
@ -240,8 +240,17 @@ public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment
} }
} }
public interface OnUiInitializedAdapter { @Override
void onUiInitialized(); public void onDestroy() {
if (dialogStateListener != null) {
dialogStateListener.onCloseDialog();
}
super.onDestroy();
}
public interface DialogStateListener {
void onDialogCreated();
void onCloseDialog();
} }
public interface OnApplySelectionListener { public interface OnApplySelectionListener {

View file

@ -1,12 +1,9 @@
package net.osmand.plus.development; package net.osmand.plus.development;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.util.Pair;
import android.util.Patterns;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
@ -17,56 +14,58 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnFilesUploadCallback;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.UiUtilities; import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType; import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.OsmandActionBarActivity; import net.osmand.plus.activities.OsmandActionBarActivity;
import net.osmand.plus.backup.BackupHelper;
import net.osmand.plus.backup.BackupHelper.BackupInfo;
import net.osmand.plus.backup.BackupHelper.OnRegisterUserListener;
import net.osmand.plus.backup.BackupTask;
import net.osmand.plus.backup.BackupTask.OnBackupListener;
import net.osmand.plus.backup.GpxFileInfo;
import net.osmand.plus.backup.PrepareBackupTask;
import net.osmand.plus.backup.PrepareBackupTask.OnPrepareBackupListener;
import net.osmand.plus.backup.UserFile;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.OsmandTextFieldBoxes;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.text.DateFormat;
import java.util.HashMap; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
public class TestBackupActivity extends OsmandActionBarActivity { public class TestBackupActivity extends OsmandActionBarActivity {
// TODO pass actual sub order id! private static final DateFormat DF = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
private static final String TEST_ORDER_ID = "";
private OsmandApplication app; private OsmandApplication app;
private OsmandSettings settings; private OsmandSettings settings;
private BackupHelper backupHelper;
private ProgressBar progressBar; private ProgressBar progressBar;
private View buttonRegister; private View buttonRegister;
private View buttonVerify; private View buttonVerify;
private View buttonRefresh;
private View buttonBackup; private View buttonBackup;
private View buttonRestore; private View buttonRestore;
private EditText emailEditText; private EditText emailEditText;
private OsmandTextFieldBoxes tokenEdit;
private EditText tokenEditText; private EditText tokenEditText;
private TextView infoView; private TextView infoView;
public interface OnResultListener { private BackupInfo backupInfo;
void onResult(boolean success, @Nullable String result);
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
app = getMyApplication(); app = getMyApplication();
settings = app.getSettings(); settings = app.getSettings();
backupHelper = app.getBackupHelper();
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this); final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
boolean nightMode = !app.getSettings().isLightContent(); boolean nightMode = !app.getSettings().isLightContent();
@ -90,15 +89,23 @@ public class TestBackupActivity extends OsmandActionBarActivity {
} }
}); });
if (!backupHelper.hasOsmLiveUpdates()) {
findViewById(R.id.main_view).setVisibility(View.GONE);
return;
}
buttonRegister = findViewById(R.id.btn_register); buttonRegister = findViewById(R.id.btn_register);
UiUtilities.setupDialogButton(nightMode, buttonRegister, DialogButtonType.PRIMARY, "Register"); UiUtilities.setupDialogButton(nightMode, buttonRegister, DialogButtonType.PRIMARY, "Register");
buttonVerify = findViewById(R.id.btn_verify); buttonVerify = findViewById(R.id.btn_verify);
UiUtilities.setupDialogButton(nightMode, buttonVerify, DialogButtonType.PRIMARY, "Verify"); UiUtilities.setupDialogButton(nightMode, buttonVerify, DialogButtonType.PRIMARY, "Verify");
buttonRefresh = findViewById(R.id.btn_refresh);
UiUtilities.setupDialogButton(nightMode, buttonRefresh, DialogButtonType.PRIMARY, "Refresh");
buttonBackup = findViewById(R.id.btn_backup); buttonBackup = findViewById(R.id.btn_backup);
UiUtilities.setupDialogButton(nightMode, buttonBackup, DialogButtonType.PRIMARY, "Backup"); UiUtilities.setupDialogButton(nightMode, buttonBackup, DialogButtonType.PRIMARY, "Backup");
buttonRestore = findViewById(R.id.btn_restore); buttonRestore = findViewById(R.id.btn_restore);
UiUtilities.setupDialogButton(nightMode, buttonRestore, DialogButtonType.PRIMARY, "Restore"); UiUtilities.setupDialogButton(nightMode, buttonRestore, DialogButtonType.PRIMARY, "Restore");
tokenEdit = findViewById(R.id.edit_token_label);
tokenEditText = findViewById(R.id.edit_token); tokenEditText = findViewById(R.id.edit_token);
infoView = findViewById(R.id.text_info); infoView = findViewById(R.id.text_info);
progressBar = findViewById(R.id.progress_bar); progressBar = findViewById(R.id.progress_bar);
@ -109,22 +116,31 @@ public class TestBackupActivity extends OsmandActionBarActivity {
if (!Algorithms.isEmpty(email)) { if (!Algorithms.isEmpty(email)) {
emailEditText.setText(email); emailEditText.setText(email);
} }
if (backupHelper.isRegistered()) {
tokenEdit.setVisibility(View.GONE);
buttonVerify.setVisibility(View.GONE);
} else {
tokenEdit.setVisibility(View.VISIBLE);
buttonVerify.setVisibility(View.VISIBLE);
}
buttonRegister.setOnClickListener(new View.OnClickListener() { buttonRegister.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
String email = emailEditText.getText().toString(); String email = emailEditText.getText().toString();
if (isEmailValid(email)) { if (AndroidUtils.isValidEmail(email)) {
buttonRegister.setEnabled(false); buttonRegister.setEnabled(false);
settings.BACKUP_USER_EMAIL.set(email); settings.BACKUP_USER_EMAIL.set(email);
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
registerUser(email, new OnResultListener() { backupHelper.registerUser(email, new OnRegisterUserListener() {
@Override @Override
public void onResult(boolean success, @Nullable String result) { public void onRegisterUser(int status, @Nullable String message) {
TestBackupActivity a = activityRef.get(); TestBackupActivity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) { if (AndroidUtils.isActivityNotDestroyed(a)) {
a.progressBar.setVisibility(View.GONE); a.progressBar.setVisibility(View.GONE);
a.buttonRegister.setEnabled(!success); a.buttonRegister.setEnabled(status != BackupHelper.STATUS_SUCCESS);
a.buttonVerify.setEnabled(success); a.tokenEdit.setVisibility(View.VISIBLE);
a.buttonVerify.setVisibility(View.VISIBLE);
a.buttonVerify.setEnabled(status == BackupHelper.STATUS_SUCCESS);
a.tokenEditText.requestFocus(); a.tokenEditText.requestFocus();
} }
} }
@ -139,17 +155,22 @@ public class TestBackupActivity extends OsmandActionBarActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
String token = tokenEditText.getText().toString(); String token = tokenEditText.getText().toString();
if (isTokenValid(token)) { if (BackupHelper.isTokenValid(token)) {
buttonVerify.setEnabled(false); buttonVerify.setEnabled(false);
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
registerDevice(token, new OnResultListener() { backupHelper.registerDevice(token, new BackupHelper.OnRegisterDeviceListener() {
@Override @Override
public void onResult(boolean success, @Nullable String result) { public void onRegisterDevice(int status, @Nullable String message) {
TestBackupActivity a = activityRef.get(); TestBackupActivity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) { if (AndroidUtils.isActivityNotDestroyed(a)) {
a.progressBar.setVisibility(View.GONE); a.progressBar.setVisibility(View.GONE);
a.buttonVerify.setEnabled(!success); a.buttonVerify.setEnabled(status != BackupHelper.STATUS_SUCCESS);
a.loadBackupInfo(); if (status == BackupHelper.STATUS_SUCCESS) {
tokenEdit.setVisibility(View.GONE);
buttonVerify.setVisibility(View.GONE);
}
a.prepareBackup();
} }
} }
}); });
@ -160,262 +181,163 @@ public class TestBackupActivity extends OsmandActionBarActivity {
} }
} }
}); });
buttonRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
prepareBackup();
}
});
buttonBackup.setOnClickListener(new View.OnClickListener() { buttonBackup.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
uploadFiles(); if (backupInfo != null) {
BackupTask task = new BackupTask(backupInfo, TestBackupActivity.this, new OnBackupListener() {
@Override
public void onBackupDone(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors,
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
TestBackupActivity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
String description;
if (error != null) {
description = error;
} else if (uploadErrors == null && downloadErrors == null) {
description = "No data";
} else {
description = getBackupErrorsDescription(uploadErrors, downloadErrors, deleteErrors, error);
}
a.infoView.setText(description);
a.infoView.requestFocus();
a.prepareBackup();
}
}
});
task.runBackup();
}
} }
}); });
buttonRestore.setOnClickListener(new View.OnClickListener() { buttonRestore.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (backupInfo != null) {
BackupTask task = new BackupTask(backupInfo, TestBackupActivity.this, new OnBackupListener() {
@Override
public void onBackupDone(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors,
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
TestBackupActivity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
String description;
if (error != null) {
description = error;
} else if (uploadErrors == null && downloadErrors == null) {
description = "No data";
} else {
description = getBackupErrorsDescription(uploadErrors, downloadErrors, deleteErrors, error);
}
a.infoView.setText(description);
a.infoView.requestFocus();
a.prepareBackup();
}
}
});
task.runRestore();
}
} }
}); });
loadBackupInfo(); prepareBackup();
} }
private void loadBackupInfo() { private String getBackupErrorsDescription(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors, @Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
if (!Algorithms.isEmpty(getDeviceId()) && !Algorithms.isEmpty(getAccessToken())) { StringBuilder sb = new StringBuilder();
if (!Algorithms.isEmpty(uploadErrors)) {
sb.append("--- Upload errors ---").append("\n");
for (Entry<File, String> uploadEntry : uploadErrors.entrySet()) {
sb.append(uploadEntry.getKey().getName()).append(": ").append(uploadEntry.getValue()).append("\n");
}
}
if (!Algorithms.isEmpty(downloadErrors)) {
sb.append("--- Download errors ---").append("\n");
for (Entry<File, String> downloadEntry : downloadErrors.entrySet()) {
sb.append(downloadEntry.getKey().getName()).append(": ").append(downloadEntry.getValue()).append("\n");
}
}
if (!Algorithms.isEmpty(deleteErrors)) {
sb.append("--- Delete errors ---").append("\n");
for (Entry<UserFile, String> deleteEntry : deleteErrors.entrySet()) {
sb.append(deleteEntry.getKey().getName()).append(": ").append(deleteEntry.getValue()).append("\n");
}
}
return sb.length() == 0 ? "OK" : sb.toString();
}
private String getBackupDescription(@NonNull BackupInfo backupInfo) {
StringBuilder sb = new StringBuilder();
if (!Algorithms.isEmpty(backupInfo.filesToUpload)) {
sb.append("\n").append("--- Upload ---").append("\n");
for (GpxFileInfo info : backupInfo.filesToUpload) {
sb.append(info.getFileName(true))
.append(" L: ").append(DF.format(new Date(info.getFileDate())))
.append(" U: ").append(DF.format(new Date(info.uploadTime)))
.append("\n");
}
}
if (!Algorithms.isEmpty(backupInfo.filesToDownload)) {
sb.append("\n").append("--- Download ---").append("\n");
for (UserFile userFile : backupInfo.filesToDownload) {
sb.append(userFile.getName())
.append(" R: ").append(DF.format(new Date(userFile.getClienttimems())))
.append("\n");
}
}
if (!Algorithms.isEmpty(backupInfo.filesToDelete)) {
sb.append("\n").append("--- Delete ---").append("\n");
for (UserFile userFile : backupInfo.filesToDelete) {
sb.append(userFile.getName())
.append(" R: ").append(DF.format(new Date(userFile.getClienttimems())))
.append("\n");
}
}
if (!Algorithms.isEmpty(backupInfo.filesToMerge)) {
sb.append("\n").append("--- Conflicts ---").append("\n");
for (Pair<GpxFileInfo, UserFile> localRemote : backupInfo.filesToMerge) {
GpxFileInfo local = localRemote.first;
UserFile remote = localRemote.second;
sb.append(local.getFileName(true))
.append(" L: ").append(DF.format(new Date(local.getFileDate())))
.append(" U: ").append(DF.format(new Date(local.uploadTime)))
.append(" R: ").append(DF.format(new Date(remote.getClienttimems())))
.append("\n");
}
}
return sb.toString();
}
private void prepareBackup() {
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this); final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
progressBar.setVisibility(View.VISIBLE); PrepareBackupTask prepareBackupTask = new PrepareBackupTask(this, new OnPrepareBackupListener() {
loadBackupInfo(new OnResultListener() {
@Override @Override
public void onResult(boolean success, @Nullable String result) { public void onBackupPrepared(@Nullable BackupInfo backupInfo, @Nullable String error) {
TestBackupActivity.this.backupInfo = backupInfo;
TestBackupActivity a = activityRef.get(); TestBackupActivity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) { if (AndroidUtils.isActivityNotDestroyed(a)) {
a.progressBar.setVisibility(View.GONE); String description = "Last uploaded: " + DF.format(new Date(settings.BACKUP_LAST_UPLOADED_TIME.get())) + "\n\n";
a.infoView.setText(result); if (error != null) {
description += error;
} else if (backupInfo == null) {
description += "No data";
} else {
description += "Files to upload: " + backupInfo.filesToUpload.size()
+ "\nFiles to download: " + backupInfo.filesToDownload.size()
+ "\nFiles to delete: " + backupInfo.filesToDelete.size()
+ "\nConflicts: " + backupInfo.filesToMerge.size()
+ "\n" + getBackupDescription(backupInfo);
}
a.infoView.setText(description);
a.infoView.requestFocus(); a.infoView.requestFocus();
} }
} }
}); });
} prepareBackupTask.prepare();
}
private boolean isEmailValid(CharSequence target) {
return (!TextUtils.isEmpty(target) && Patterns.EMAIL_ADDRESS.matcher(target).matches());
}
private String getOrderId() {
return TEST_ORDER_ID;
}
private String getDeviceId() {
return settings.BACKUP_DEVICE_ID.get();
}
private String getAccessToken() {
return settings.BACKUP_ACCESS_TOKEN.get();
}
private void registerUser(@NonNull String email, @Nullable final OnResultListener listener) {
Map<String, String> params = new HashMap<>();
params.put("email", email);
params.put("orderid", getOrderId());
params.put("deviceid", app.getUserAndroidId());
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/user-register", params, "Register user", true, true, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
boolean success = false;
if (!Algorithms.isEmpty(resultJson)) {
try {
// {"status":"ok"}
JSONObject result = new JSONObject(resultJson);
String status = result.getString("status");
success = status.equals("ok");
app.showToastMessage(success
? "You have been registered successfully. Please check for email with activation code."
: "User registration error: " + status);
} catch (JSONException e) {
app.showToastMessage("User registration error: json parsing");
}
} else {
app.showToastMessage("User registration error: empty response");
}
if (listener != null) {
listener.onResult(success, resultJson);
}
}
});
}
private void registerDevice(String token, @Nullable final OnResultListener listener) {
Map<String, String> params = new HashMap<>();
params.put("email", settings.BACKUP_USER_EMAIL.get());
params.put("orderid", getOrderId());
params.put("deviceid", app.getUserAndroidId());
params.put("token", token);
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/device-register", params, "Register device", true, true, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
boolean success = false;
if (!Algorithms.isEmpty(resultJson)) {
try {
/*
{
"id": 1034,
"userid": 1033,
"deviceid": "2fa8080d2985a777",
"orderid": "460000687003939",
"accesstoken": "4bc0a61f-397a-4c3e-9ffc-db382ec00372",
"udpatetime": "Apr 11, 2021, 11:32:20 AM"
}
*/
JSONObject result = new JSONObject(resultJson);
settings.BACKUP_DEVICE_ID.set(result.getString("id"));
settings.BACKUP_USER_ID.set(result.getString("userid"));
settings.BACKUP_NATIVE_DEVICE_ID.set(result.getString("deviceid"));
settings.BACKUP_ACCESS_TOKEN.set(result.getString("accesstoken"));
settings.BACKUP_ACCESS_TOKEN_UPDATE_TIME.set(result.getString("udpatetime"));
success = true;
app.showToastMessage("Device have been registered successfully");
} catch (JSONException e) {
app.showToastMessage("Device registration error: json parsing");
}
} else {
app.showToastMessage("Device registration error: empty response");
}
if (listener != null) {
listener.onResult(success, resultJson);
}
}
});
}
private void uploadFiles() {
LoadGpxTask loadGpxTask = new LoadGpxTask(this, new LoadGpxTask.OnLoadGpxListener() {
@Override
public void onLoadGpxDone(@NonNull List<GpxInfo> result) {
uploadFiles(result);
}
});
loadGpxTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
private void uploadFiles(List<GpxInfo> gpxFiles) {
//{"status":"ok"}
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
Map<String, String> params = new HashMap<>();
params.put("deviceid", getDeviceId());
params.put("accessToken", getAccessToken());
Map<String, String> headers = new HashMap<>();
headers.put("Accept-Encoding", "deflate, gzip");
final Map<File, GpxInfo> gpxInfos = new HashMap<>();
for (GpxInfo gpxFile : gpxFiles) {
gpxInfos.put(gpxFile.file, gpxFile);
}
final List<File> files = new ArrayList<>(gpxInfos.keySet());
File favoritesFile = app.getFavorites().getExternalFile();
files.add(favoritesFile);
final ProgressImplementation progress = ProgressImplementation.createProgressDialog(this,
"Create backup", "Uploading " + files.size() + " file(s) to server", ProgressDialog.STYLE_HORIZONTAL);
AndroidNetworkUtils.uploadFilesAsync("https://osmand.net/userdata/upload-file", files, true, params, headers, new OnFilesUploadCallback() {
@Nullable
@Override
public Map<String, String> getAdditionalParams(@NonNull File file) {
GpxInfo gpxInfo = gpxInfos.get(file);
Map<String, String> additionaParams = new HashMap<>();
additionaParams.put("name", gpxInfo == null ? file.getName() : gpxInfo.getFileName(true));
additionaParams.put("type", Algorithms.getFileExtension(file));
return additionaParams;
}
@Override
public void onFileUploadProgress(@NonNull File file, int percent) {
Activity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
if (percent < 100) {
progress.startTask(file.getName(), percent);
} else {
progress.finishTask();
}
}
}
@Override
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
Activity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
}, 300);
app.showToastMessage("Uploaded " + (files.size() - errors.size() + " files" +
(errors.size() > 0 ? ". Errors: " + errors.size() : "")));
loadBackupInfo();
}
}
});
}
private void loadBackupInfo(@Nullable final OnResultListener listener) {
Map<String, String> params = new HashMap<>();
params.put("deviceid", getDeviceId());
params.put("accessToken", getAccessToken());
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/list-files", params, "Get backup info", true, false, new OnRequestResultListener() {
@Override
public void onResult(String resultJson) {
boolean success = false;
StringBuilder resultString = new StringBuilder();
if (!Algorithms.isEmpty(resultJson)) {
try {
/*
{
"totalZipSize": 21792,
"totalFileSize": 185920,
"totalFiles": 1,
"totalFileVersions": 2,
"uniqueFiles": [
{
"userid": 1033,
"id": 7,
"deviceid": 1034,
"filesize": 92960,
"type": "gpx",
"name": "test/Day 2.gpx",
"updatetime": "Apr 11, 2021, 1:49:01 PM",
"updatetimems": 1618141741822,
"zipSize": 10896
}
],
"deviceid": 1034
}
*/
JSONObject result = new JSONObject(resultJson);
String totalZipSize = result.getString("totalZipSize");
String totalFiles = result.getString("totalFiles");
String totalFileVersions = result.getString("totalFileVersions");
JSONArray files = result.getJSONArray("uniqueFiles");
resultString.append("Total files: ").append(totalFiles).append("\n");
resultString.append("Total zip size: ").append(AndroidUtils.formatSize(app, Long.parseLong(totalZipSize))).append("\n");
resultString.append("Total file versions: ").append(totalFileVersions);
success = true;
} catch (JSONException e) {
}
}
if (listener != null) {
listener.onResult(success, resultString.toString());
}
}
});
}
private boolean isTokenValid(String token) {
return token.matches("[0-9]+");
} }
private int resolveResourceId(final Activity activity, final int attr) { private int resolveResourceId(final Activity activity, final int attr) {
@ -423,173 +345,4 @@ public class TestBackupActivity extends OsmandActionBarActivity {
activity.getTheme().resolveAttribute(attr, typedvalueattr, true); activity.getTheme().resolveAttribute(attr, typedvalueattr, true);
return typedvalueattr.resourceId; return typedvalueattr.resourceId;
} }
private static class LoadGpxTask extends AsyncTask<Activity, GpxInfo, List<GpxInfo>> {
private final OsmandApplication app;
private final OnLoadGpxListener listener;
private final WeakReference<Activity> activityRef;
private List<GpxInfo> result;
private ProgressImplementation progress;
interface OnLoadGpxListener {
void onLoadGpxDone(@NonNull List<GpxInfo> result);
}
LoadGpxTask(@NonNull Activity activity, @Nullable OnLoadGpxListener listener) {
this.activityRef = new WeakReference<>(activity);
this.app = (OsmandApplication) activity.getApplication();
this.listener = listener;
}
public List<GpxInfo> getResult() {
return result;
}
@NonNull
@Override
protected List<GpxInfo> doInBackground(Activity... params) {
List<GpxInfo> result = new ArrayList<>();
loadGPXData(app.getAppPath(IndexConstants.GPX_INDEX_DIR), result, this);
return result;
}
public void loadFile(GpxInfo... loaded) {
publishProgress(loaded);
}
@Override
protected void onPreExecute() {
Activity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
progress = ProgressImplementation.createProgressDialog(a,
"Create backup", "Collecting gpx files...", ProgressDialog.STYLE_HORIZONTAL);
}
}
@Override
protected void onProgressUpdate(GpxInfo... values) {
Activity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
progress.startTask(values[0].getFileName(true), -1);
}
}
@Override
protected void onPostExecute(@NonNull List<GpxInfo> result) {
this.result = result;
if (listener != null) {
listener.onLoadGpxDone(result);
}
Activity a = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(a)) {
progress.finishTask();
app.runInUIThread(new Runnable() {
@Override
public void run() {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
}, 300);
}
}
private void loadGPXData(File mapPath, List<GpxInfo> result, LoadGpxTask loadTask) {
if (mapPath.canRead()) {
List<GpxInfo> progress = new ArrayList<>();
loadGPXFolder(mapPath, result, loadTask, progress, "");
if (!progress.isEmpty()) {
loadTask.loadFile(progress.toArray(new GpxInfo[0]));
}
}
}
private void loadGPXFolder(File mapPath, List<GpxInfo> result, LoadGpxTask loadTask, List<GpxInfo> progress,
String gpxSubfolder) {
File[] listFiles = mapPath.listFiles();
if (listFiles != null) {
for (File gpxFile : listFiles) {
if (gpxFile.isDirectory()) {
String sub = gpxSubfolder.length() == 0 ? gpxFile.getName() : gpxSubfolder + "/"
+ gpxFile.getName();
loadGPXFolder(gpxFile, result, loadTask, progress, sub);
} else if (gpxFile.isFile() && gpxFile.getName().toLowerCase().endsWith(IndexConstants.GPX_FILE_EXT)) {
GpxInfo info = new GpxInfo();
info.subfolder = gpxSubfolder;
info.file = gpxFile;
result.add(info);
progress.add(info);
if (progress.size() > 7) {
loadTask.loadFile(progress.toArray(new GpxInfo[0]));
progress.clear();
}
}
}
}
}
}
private static class GpxInfo {
public File file;
public String subfolder;
private String name = null;
private int sz = -1;
private String fileName = null;
public String getName() {
if (name == null) {
name = formatName(file.getName());
}
return name;
}
private String formatName(String name) {
int ext = name.lastIndexOf('.');
if (ext != -1) {
name = name.substring(0, ext);
}
return name.replace('_', ' ');
}
// Usage: AndroidUtils.formatSize(v.getContext(), getSize() * 1024l);
public int getSize() {
if (sz == -1) {
if (file == null) {
return -1;
}
sz = (int) ((file.length() + 512) >> 10);
}
return sz;
}
public long getFileDate() {
if (file == null) {
return 0;
}
return file.lastModified();
}
public String getFileName(boolean includeSubfolder) {
String result;
if (fileName != null) {
result = fileName;
} else {
if (file == null) {
result = "";
} else {
result = fileName = file.getName();
}
}
if (includeSubfolder && !Algorithms.isEmpty(subfolder)) {
result = subfolder + "/" + result;
}
return result;
}
}
} }

View file

@ -7,6 +7,7 @@ import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.Version; import net.osmand.plus.Version;
import net.osmand.plus.download.IndexItem.DownloadEntry;
import net.osmand.plus.helpers.FileNameTranslationHelper; import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -213,8 +214,8 @@ public class DownloadFileHelper {
} }
unzipFile(de, progress, downloadInputStreams); unzipFile(de, progress, downloadInputStreams);
if (!de.targetFile.getAbsolutePath().equals(de.fileToDownload.getAbsolutePath())) { if (!de.targetFile.getAbsolutePath().equals(de.fileToDownload.getAbsolutePath())) {
boolean successfull = Algorithms.removeAllFiles(de.targetFile); boolean successful = Algorithms.removeAllFiles(de.targetFile);
if (successfull) { if (successful) {
ctx.getResourceManager().closeFile(de.targetFile.getName()); ctx.getResourceManager().closeFile(de.targetFile.getName());
} }
@ -226,6 +227,8 @@ public class DownloadFileHelper {
} }
if (de.type == DownloadActivityType.VOICE_FILE) { if (de.type == DownloadActivityType.VOICE_FILE) {
copyVoiceConfig(de); copyVoiceConfig(de);
} else if (de.type == DownloadActivityType.SRTM_COUNTRY_FILE) {
removePreviousSrtmFile(de);
} }
toReIndex.add(de.targetFile); toReIndex.add(de.targetFile);
return true; return true;
@ -238,6 +241,26 @@ public class DownloadFileHelper {
} }
} }
private void removePreviousSrtmFile(DownloadEntry entry) {
String meterExt = IndexConstants.BINARY_SRTM_MAP_INDEX_EXT;
String feetExt = IndexConstants.BINARY_SRTM_FEET_MAP_INDEX_EXT;
String fileName = entry.targetFile.getAbsolutePath();
if (fileName.endsWith(meterExt)) {
fileName = fileName.replace(meterExt, feetExt);
} else if (fileName.endsWith(feetExt)) {
fileName = fileName.replace(feetExt, meterExt);
}
File previous = new File(fileName);
if (previous != null && previous.exists()) {
boolean successful = Algorithms.removeAllFiles(previous);
if (successful) {
ctx.getResourceManager().closeFile(previous.getName());
}
}
}
private void copyVoiceConfig(IndexItem.DownloadEntry de) { private void copyVoiceConfig(IndexItem.DownloadEntry de) {
File f = ctx.getAppPath("/voice/" + de.baseName + "/_config.p"); File f = ctx.getAppPath("/voice/" + de.baseName + "/_config.p");
if (f.exists()) try { if (f.exists()) try {
@ -401,8 +424,5 @@ public class DownloadFileHelper {
count = 0; count = 0;
return last; return last;
} }
} }
} }

View file

@ -490,7 +490,7 @@ public class DownloadResources extends DownloadResourceGroup {
private void replaceIndividualSrtmWithGroups(@NonNull WorldRegion region) { private void replaceIndividualSrtmWithGroups(@NonNull WorldRegion region) {
DownloadResourceGroup group = getRegionMapsGroup(region); DownloadResourceGroup group = getRegionMapsGroup(region);
if (group != null) { if (group != null) {
boolean useMetersByDefault = SrtmDownloadItem.shouldUseMetersByDefault(app); boolean useMetersByDefault = SrtmDownloadItem.isUseMetricByDefault(app);
boolean listModified = false; boolean listModified = false;
DownloadActivityType srtmType = DownloadActivityType.SRTM_COUNTRY_FILE; DownloadActivityType srtmType = DownloadActivityType.SRTM_COUNTRY_FILE;
List<DownloadItem> individualItems = group.getIndividualDownloadItems(); List<DownloadItem> individualItems = group.getIndividualDownloadItems();

View file

@ -233,6 +233,9 @@ public class IndexItem extends DownloadItem implements Comparable<IndexItem> {
@Nullable @Nullable
@Override @Override
public String getAdditionalDescription(Context ctx) { public String getAdditionalDescription(Context ctx) {
if (getType() == DownloadActivityType.SRTM_COUNTRY_FILE) {
return SrtmDownloadItem.getAbbreviationInScopes(ctx, this);
}
return null; return null;
} }

View file

@ -139,7 +139,7 @@ public class MultipleDownloadItem extends DownloadItem {
if (obj instanceof IndexItem) { if (obj instanceof IndexItem) {
return (IndexItem) obj; return (IndexItem) obj;
} else if (obj instanceof SrtmDownloadItem) { } else if (obj instanceof SrtmDownloadItem) {
return ((SrtmDownloadItem) obj).getIndexItem(); return ((SrtmDownloadItem) obj).getDefaultIndexItem();
} }
return null; return null;
} }
@ -147,9 +147,6 @@ public class MultipleDownloadItem extends DownloadItem {
@Nullable @Nullable
@Override @Override
public String getAdditionalDescription(Context ctx) { public String getAdditionalDescription(Context ctx) {
for (DownloadItem item : items) {
return item.getAdditionalDescription(ctx);
}
return null; return null;
} }

View file

@ -13,7 +13,7 @@ import net.osmand.plus.base.ModeSelectionBottomSheet;
import net.osmand.plus.base.MultipleSelectionWithModeBottomSheet; import net.osmand.plus.base.MultipleSelectionWithModeBottomSheet;
import net.osmand.plus.base.SelectionBottomSheet; import net.osmand.plus.base.SelectionBottomSheet;
import net.osmand.plus.base.SelectionBottomSheet.OnApplySelectionListener; import net.osmand.plus.base.SelectionBottomSheet.OnApplySelectionListener;
import net.osmand.plus.base.SelectionBottomSheet.OnUiInitializedAdapter; import net.osmand.plus.base.SelectionBottomSheet.DialogStateListener;
import net.osmand.plus.base.SelectionBottomSheet.SelectableItem; import net.osmand.plus.base.SelectionBottomSheet.SelectableItem;
import net.osmand.plus.widgets.multistatetoggle.RadioItem; import net.osmand.plus.widgets.multistatetoggle.RadioItem;
import net.osmand.plus.widgets.multistatetoggle.RadioItem.OnRadioItemClickListener; import net.osmand.plus.widgets.multistatetoggle.RadioItem.OnRadioItemClickListener;
@ -22,11 +22,12 @@ import net.osmand.util.Algorithms;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import static net.osmand.plus.download.MultipleDownloadItem.getIndexItem; import static net.osmand.plus.download.MultipleDownloadItem.getIndexItem;
public class SelectIndexesUiHelper { public class SelectIndexesHelper {
private final OsmandApplication app; private final OsmandApplication app;
private final AppCompatActivity activity; private final AppCompatActivity activity;
@ -34,58 +35,70 @@ public class SelectIndexesUiHelper {
private final ItemsToDownloadSelectedListener listener; private final ItemsToDownloadSelectedListener listener;
private final DateFormat dateFormat; private final DateFormat dateFormat;
private final boolean showRemoteDate; private final boolean showRemoteDate;
private final List<DownloadItem> itemsToDownload;
private final DownloadItem downloadItem; private final DownloadItem downloadItem;
private final boolean useMetricByDefault;
private SelectionBottomSheet dialog; private SelectionBottomSheet dialog;
private SelectIndexesUiHelper(@NonNull DownloadItem downloadItem, private SelectIndexesHelper(@NonNull DownloadItem downloadItem,
@NonNull AppCompatActivity activity, @NonNull AppCompatActivity activity,
@NonNull DateFormat dateFormat, @NonNull DateFormat dateFormat,
boolean showRemoteDate, boolean showRemoteDate,
@NonNull ItemsToDownloadSelectedListener listener) { @NonNull ItemsToDownloadSelectedListener listener) {
this.app = (OsmandApplication) activity.getApplicationContext(); this.app = (OsmandApplication) activity.getApplicationContext();
this.activity = activity; this.activity = activity;
this.downloadItem = downloadItem;
this.dateFormat = dateFormat; this.dateFormat = dateFormat;
this.showRemoteDate = showRemoteDate; this.showRemoteDate = showRemoteDate;
this.listener = listener; this.listener = listener;
this.downloadItem = downloadItem;
this.itemsToDownload = getItemsToDownload(downloadItem);
this.useMetricByDefault = SrtmDownloadItem.isUseMetricByDefault(app);
} }
public static void showDialog(@NonNull DownloadItem i, public static void showDialog(@NonNull DownloadItem di,
@NonNull AppCompatActivity a, @NonNull AppCompatActivity a,
@NonNull DateFormat df, @NonNull DateFormat df,
boolean showRemoteDate, boolean showRemoteDate,
@NonNull ItemsToDownloadSelectedListener l) { @NonNull ItemsToDownloadSelectedListener l) {
new SelectIndexesUiHelper(i, a, df, showRemoteDate, l).showDialogInternal(); SelectIndexesHelper h = new SelectIndexesHelper(di, a, df, showRemoteDate, l);
} if (di.getType() == DownloadActivityType.SRTM_COUNTRY_FILE) {
if (di instanceof MultipleDownloadItem) {
private void showDialogInternal() { h.showSrtmMultipleSelectionDialog();
if (downloadItem.getType() == DownloadActivityType.SRTM_COUNTRY_FILE) {
if (downloadItem instanceof MultipleDownloadItem) {
showSrtmMultipleSelectionDialog();
} else { } else {
showSrtmModeSelectionDialog(); h.showSrtmTypeSelectionDialog();
} }
} else if (downloadItem instanceof MultipleDownloadItem) { } else if (di instanceof MultipleDownloadItem) {
showMultipleSelectionDialog(); h.showMultipleSelectionDialog();
} }
} }
private void showMultipleSelectionDialog() { private void showMultipleSelectionDialog() {
MultipleDownloadItem mdi = (MultipleDownloadItem) downloadItem;
List<SelectableItem> allItems = new ArrayList<>(); List<SelectableItem> allItems = new ArrayList<>();
List<SelectableItem> selectedItems = new ArrayList<>(); List<SelectableItem> selectedItems = new ArrayList<>();
prepareItems(allItems, selectedItems);
MultipleSelectionBottomSheet msDialog = MultipleSelectionBottomSheet.showInstance( for (DownloadItem di : mdi.getAllItems()) {
activity, allItems, selectedItems, true); SelectableItem si = createSelectableItem(di);
allItems.add(si);
if (itemsToDownload.contains(di)) {
selectedItems.add(si);
}
}
MultipleSelectionBottomSheet msDialog =
MultipleSelectionBottomSheet.showInstance(activity, allItems, selectedItems, true);
this.dialog = msDialog; this.dialog = msDialog;
msDialog.setOnUiInitializedAdapter(new OnUiInitializedAdapter() { msDialog.setDialogStateListener(new DialogStateListener() {
@Override @Override
public void onUiInitialized() { public void onDialogCreated() {
dialog.setTitle(app.getString(R.string.welmode_download_maps)); dialog.setTitle(app.getString(R.string.welmode_download_maps));
} }
@Override
public void onCloseDialog() { }
}); });
msDialog.setSelectionUpdateListener(new SelectionUpdateListener() { msDialog.setSelectionUpdateListener(new SelectionUpdateListener() {
@ -99,25 +112,40 @@ public class SelectIndexesUiHelper {
} }
private void showSrtmMultipleSelectionDialog() { private void showSrtmMultipleSelectionDialog() {
MultipleDownloadItem mdi = (MultipleDownloadItem) downloadItem;
List<SelectableItem> allItems = new ArrayList<>(); List<SelectableItem> allItems = new ArrayList<>();
List<SelectableItem> selectedItems = new ArrayList<>(); List<SelectableItem> selectedItems = new ArrayList<>();
prepareItems(allItems, selectedItems);
SrtmDownloadItem srtmItem = (SrtmDownloadItem) ((MultipleDownloadItem)downloadItem).getAllItems().get(0); for (DownloadItem di : mdi.getAllItems()) {
final int selectedModeOrder = srtmItem.isUseMetric() ? 0 : 1; SelectableItem si = createSrtmSelectableItem((SrtmDownloadItem) di);
final List<RadioItem> radioItems = createSrtmRadioItems(); allItems.add(si);
if (itemsToDownload.contains(di)) {
selectedItems.add(si);
}
}
final RadioItem meterBtn = createSrtmRadioBtn(true);
final RadioItem feetBtn = createSrtmRadioBtn(false);
List<RadioItem> radioItems = new ArrayList<>();
radioItems.add(meterBtn);
radioItems.add(feetBtn);
MultipleSelectionBottomSheet msDialog = MultipleSelectionWithModeBottomSheet.showInstance( MultipleSelectionBottomSheet msDialog = MultipleSelectionWithModeBottomSheet.showInstance(
activity, allItems, selectedItems, radioItems, true); activity, allItems, selectedItems, radioItems, true);
this.dialog = msDialog; this.dialog = msDialog;
msDialog.setOnUiInitializedAdapter(new OnUiInitializedAdapter() { msDialog.setDialogStateListener(new DialogStateListener() {
@Override @Override
public void onUiInitialized() { public void onDialogCreated() {
dialog.setTitle(app.getString(R.string.welmode_download_maps)); dialog.setTitle(app.getString(R.string.welmode_download_maps));
dialog.setSelectedMode(radioItems.get(selectedModeOrder)); dialog.setSelectedMode(useMetricByDefault ? meterBtn : feetBtn);
dialog.setSecondaryDescription(app.getString(R.string.srtm_download_list_help_message)); dialog.setSecondaryDescription(app.getString(R.string.srtm_download_list_help_message));
} }
@Override
public void onCloseDialog() {
resetUseMeters();
}
}); });
msDialog.setSelectionUpdateListener(new SelectionUpdateListener() { msDialog.setSelectionUpdateListener(new SelectionUpdateListener() {
@ -130,58 +158,47 @@ public class SelectIndexesUiHelper {
msDialog.setOnApplySelectionListener(getOnApplySelectionListener(listener)); msDialog.setOnApplySelectionListener(getOnApplySelectionListener(listener));
} }
private void showSrtmModeSelectionDialog() { private void showSrtmTypeSelectionDialog() {
SrtmDownloadItem srtmItem = (SrtmDownloadItem) downloadItem; SrtmDownloadItem srtmItem = (SrtmDownloadItem) downloadItem;
final int selectedModeOrder = srtmItem.isUseMetric() ? 0 : 1;
final List<RadioItem> radioItems = createSrtmRadioItems(); final RadioItem meterBtn = createSrtmRadioBtn(true);
SelectableItem preview = createSelectableItem(srtmItem); final RadioItem feetBtn = createSrtmRadioBtn(false);
List<RadioItem> radioItems = new ArrayList<>();
radioItems.add(meterBtn);
radioItems.add(feetBtn);
SelectableItem preview = createSrtmSelectableItem(srtmItem);
dialog = ModeSelectionBottomSheet.showInstance(activity, preview, radioItems, true); dialog = ModeSelectionBottomSheet.showInstance(activity, preview, radioItems, true);
dialog.setOnUiInitializedAdapter(new OnUiInitializedAdapter() { dialog.setDialogStateListener(new DialogStateListener() {
@Override @Override
public void onUiInitialized() { public void onDialogCreated() {
ModeSelectionBottomSheet dialog = (ModeSelectionBottomSheet) SelectIndexesUiHelper.this.dialog; ModeSelectionBottomSheet dialog = (ModeSelectionBottomSheet) SelectIndexesHelper.this.dialog;
dialog.setTitle(app.getString(R.string.srtm_unit_format)); dialog.setTitle(app.getString(R.string.srtm_unit_format));
dialog.setPrimaryDescription(app.getString(R.string.srtm_download_single_help_message)); dialog.setPrimaryDescription(app.getString(R.string.srtm_download_single_help_message));
updateSize(); updateSize();
dialog.setSelectedMode(radioItems.get(selectedModeOrder)); dialog.setSelectedMode(useMetricByDefault ? meterBtn : feetBtn);
}
@Override
public void onCloseDialog() {
resetUseMeters();
} }
}); });
dialog.setOnApplySelectionListener(getOnApplySelectionListener(listener)); dialog.setOnApplySelectionListener(getOnApplySelectionListener(listener));
} }
private void prepareItems(List<SelectableItem> allItems, private RadioItem createSrtmRadioBtn(final boolean useMeters) {
List<SelectableItem> selectedItems) { int titleId = useMeters ? R.string.shared_string_meters : R.string.shared_string_feet;
final MultipleDownloadItem multipleDownloadItem = (MultipleDownloadItem) downloadItem;
final List<DownloadItem> itemsToDownload = getItemsToDownload(multipleDownloadItem);
for (DownloadItem downloadItem : multipleDownloadItem.getAllItems()) {
SelectableItem selectableItem = createSelectableItem(downloadItem);
allItems.add(selectableItem);
if (itemsToDownload.contains(downloadItem)) {
selectedItems.add(selectableItem);
}
}
}
private List<RadioItem> createSrtmRadioItems() {
List<RadioItem> radioItems = new ArrayList<>();
radioItems.add(createSrtmRadioBtn(R.string.shared_string_meters, true));
radioItems.add(createSrtmRadioBtn(R.string.shared_string_feet, false));
return radioItems;
}
private RadioItem createSrtmRadioBtn(int titleId,
final boolean useMeters) {
String title = Algorithms.capitalizeFirstLetter(app.getString(titleId)); String title = Algorithms.capitalizeFirstLetter(app.getString(titleId));
RadioItem radioItem = new TextRadioItem(title); RadioItem radioItem = new TextRadioItem(title);
radioItem.setOnClickListener(new OnRadioItemClickListener() { radioItem.setOnClickListener(new OnRadioItemClickListener() {
@Override @Override
public boolean onRadioItemClick(RadioItem radioItem, View view) { public boolean onRadioItemClick(RadioItem radioItem, View view) {
updateDialogListItems(useMeters); setUseMetersForAllItems(useMeters);
updateListItems();
updateSize(); updateSize();
return true; return true;
} }
@ -189,22 +206,45 @@ public class SelectIndexesUiHelper {
return radioItem; return radioItem;
} }
private void updateDialogListItems(boolean useMeters) { private SelectableItem createSelectableItem(DownloadItem item) {
List<SelectableItem> items = new ArrayList<>(dialog.getAllItems()); SelectableItem selectableItem = new SelectableItem();
for (SelectableItem item : items) { updateSelectableItem(selectableItem, item);
DownloadItem downloadItem = (DownloadItem) item.getObject(); selectableItem.setObject(item);
if (downloadItem instanceof SrtmDownloadItem) { return selectableItem;
((SrtmDownloadItem) downloadItem).setUseMetric(useMeters);
updateSelectableItem(item, downloadItem);
} }
private SelectableItem createSrtmSelectableItem(SrtmDownloadItem item) {
SelectableItem selectableItem = new SelectableItem();
updateSelectableItem(selectableItem, item.getDefaultIndexItem());
selectableItem.setObject(item);
return selectableItem;
}
private void updateListItems() {
List<SelectableItem> items = new ArrayList<>(dialog.getAllItems());
for (SelectableItem selectableItem : items) {
DownloadItem di = (DownloadItem) selectableItem.getObject();
if (di instanceof SrtmDownloadItem) {
di = ((SrtmDownloadItem) di).getDefaultIndexItem();
}
updateSelectableItem(selectableItem, di);
} }
dialog.setItems(items); dialog.setItems(items);
} }
private SelectableItem createSelectableItem(DownloadItem item) { private void resetUseMeters() {
SelectableItem selectableItem = new SelectableItem(); boolean useMeters = SrtmDownloadItem.isUseMetricByDefault(app);
updateSelectableItem(selectableItem, item); setUseMetersForAllItems(useMeters);
return selectableItem; }
private void setUseMetersForAllItems(boolean useMeters) {
for (SelectableItem item : dialog.getAllItems()) {
DownloadItem downloadItem = (DownloadItem) item.getObject();
if (downloadItem instanceof SrtmDownloadItem) {
SrtmDownloadItem srtmItem = (SrtmDownloadItem) downloadItem;
srtmItem.setUseMetric(useMeters);
}
}
} }
private void updateSelectableItem(SelectableItem selectableItem, private void updateSelectableItem(SelectableItem selectableItem,
@ -221,7 +261,6 @@ public class SelectIndexesUiHelper {
selectableItem.setDescription(description); selectableItem.setDescription(description);
selectableItem.setIconId(downloadItem.getType().getIconResource()); selectableItem.setIconId(downloadItem.getType().getIconResource());
selectableItem.setObject(downloadItem);
} }
private OnApplySelectionListener getOnApplySelectionListener(final ItemsToDownloadSelectedListener listener) { private OnApplySelectionListener getOnApplySelectionListener(final ItemsToDownloadSelectedListener listener) {
@ -257,13 +296,23 @@ public class SelectIndexesUiHelper {
double totalSizeMb = 0.0d; double totalSizeMb = 0.0d;
for (SelectableItem i : selectableItems) { for (SelectableItem i : selectableItems) {
Object obj = i.getObject(); Object obj = i.getObject();
if (obj instanceof DownloadItem) { if (obj instanceof SrtmDownloadItem) {
SrtmDownloadItem srtm = (SrtmDownloadItem) obj;
totalSizeMb += srtm.getDefaultIndexItem().getSizeToDownloadInMb();
} else if (obj instanceof DownloadItem) {
totalSizeMb += ((DownloadItem) obj).getSizeToDownloadInMb(); totalSizeMb += ((DownloadItem) obj).getSizeToDownloadInMb();
} }
} }
return totalSizeMb; return totalSizeMb;
} }
private static List<DownloadItem> getItemsToDownload(DownloadItem di) {
if (di instanceof MultipleDownloadItem) {
return getItemsToDownload((MultipleDownloadItem) di);
}
return Collections.emptyList();
}
private static List<DownloadItem> getItemsToDownload(MultipleDownloadItem md) { private static List<DownloadItem> getItemsToDownload(MultipleDownloadItem md) {
if (md.hasActualDataToDownload()) { if (md.hasActualDataToDownload()) {
// download left regions // download left regions

View file

@ -53,6 +53,11 @@ public class SrtmDownloadItem extends DownloadItem {
return index; return index;
} }
} }
return getDefaultIndexItem();
}
@NonNull
public IndexItem getDefaultIndexItem() {
for (IndexItem index : indexes) { for (IndexItem index : indexes) {
if (useMetric && isMetricItem(index) || !useMetric && !isMetricItem(index)) { if (useMetric && isMetricItem(index) || !useMetric && !isMetricItem(index)) {
return index; return index;
@ -135,7 +140,7 @@ public class SrtmDownloadItem extends DownloadItem {
return getAbbreviationInScopes(ctx, this); return getAbbreviationInScopes(ctx, this);
} }
public static boolean shouldUseMetersByDefault(@NonNull OsmandApplication app) { public static boolean isUseMetricByDefault(@NonNull OsmandApplication app) {
MetricsConstants metricSystem = app.getSettings().METRIC_SYSTEM.get(); MetricsConstants metricSystem = app.getSettings().METRIC_SYSTEM.get();
return metricSystem != MetricsConstants.MILES_AND_FEET; return metricSystem != MetricsConstants.MILES_AND_FEET;
} }

View file

@ -41,8 +41,8 @@ import net.osmand.plus.download.DownloadActivityType;
import net.osmand.plus.download.DownloadResourceGroup; import net.osmand.plus.download.DownloadResourceGroup;
import net.osmand.plus.download.DownloadResources; import net.osmand.plus.download.DownloadResources;
import net.osmand.plus.download.IndexItem; import net.osmand.plus.download.IndexItem;
import net.osmand.plus.download.SelectIndexesUiHelper; import net.osmand.plus.download.SelectIndexesHelper;
import net.osmand.plus.download.SelectIndexesUiHelper.ItemsToDownloadSelectedListener; import net.osmand.plus.download.SelectIndexesHelper.ItemsToDownloadSelectedListener;
import net.osmand.plus.download.MultipleDownloadItem; import net.osmand.plus.download.MultipleDownloadItem;
import net.osmand.plus.download.ui.LocalIndexesFragment.LocalIndexOperationTask; import net.osmand.plus.download.ui.LocalIndexesFragment.LocalIndexOperationTask;
import net.osmand.plus.helpers.FileNameTranslationHelper; import net.osmand.plus.helpers.FileNameTranslationHelper;
@ -492,7 +492,7 @@ public class ItemViewHolder {
} }
private void selectIndexesToDownload(DownloadItem item) { private void selectIndexesToDownload(DownloadItem item) {
SelectIndexesUiHelper.showDialog(item, context, dateFormat, showRemoteDate, SelectIndexesHelper.showDialog(item, context, dateFormat, showRemoteDate,
new ItemsToDownloadSelectedListener() { new ItemsToDownloadSelectedListener() {
@Override @Override
public void onItemsToDownloadSelected(List<IndexItem> indexes) { public void onItemsToDownloadSelected(List<IndexItem> indexes) {

View file

@ -8,6 +8,7 @@ import androidx.fragment.app.FragmentActivity;
import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.GPXFile;
import net.osmand.data.FavouritePoint; import net.osmand.data.FavouritePoint;
import net.osmand.plus.FavouritesDbHelper; import net.osmand.plus.FavouritesDbHelper;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.base.BaseLoadAsyncTask; import net.osmand.plus.base.BaseLoadAsyncTask;
@ -17,7 +18,7 @@ import static net.osmand.plus.importfiles.ImportHelper.asFavourites;
import static net.osmand.plus.myplaces.FavoritesActivity.FAV_TAB; import static net.osmand.plus.myplaces.FavoritesActivity.FAV_TAB;
import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID; import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID;
class FavoritesImportTask extends BaseLoadAsyncTask<Void, Void, GPXFile> { public class FavoritesImportTask extends BaseLoadAsyncTask<Void, Void, GPXFile> {
private GPXFile gpxFile; private GPXFile gpxFile;
private String fileName; private String fileName;
@ -33,6 +34,12 @@ class FavoritesImportTask extends BaseLoadAsyncTask<Void, Void, GPXFile> {
@Override @Override
protected GPXFile doInBackground(Void... nothing) { protected GPXFile doInBackground(Void... nothing) {
mergeFavorites(app, gpxFile, fileName, forceImportFavourites);
return null;
}
public static void mergeFavorites(@NonNull OsmandApplication app, @NonNull GPXFile gpxFile,
@NonNull String fileName, boolean forceImportFavourites) {
List<FavouritePoint> favourites = asFavourites(app, gpxFile.getPoints(), fileName, forceImportFavourites); List<FavouritePoint> favourites = asFavourites(app, gpxFile.getPoints(), fileName, forceImportFavourites);
FavouritesDbHelper favoritesHelper = app.getFavorites(); FavouritesDbHelper favoritesHelper = app.getFavorites();
checkDuplicateNames(favourites); checkDuplicateNames(favourites);
@ -42,10 +49,9 @@ class FavoritesImportTask extends BaseLoadAsyncTask<Void, Void, GPXFile> {
} }
favoritesHelper.sortAll(); favoritesHelper.sortAll();
favoritesHelper.saveCurrentPointsIntoFile(); favoritesHelper.saveCurrentPointsIntoFile();
return null;
} }
public void checkDuplicateNames(List<FavouritePoint> favourites) { public static void checkDuplicateNames(List<FavouritePoint> favourites) {
for (FavouritePoint fp : favourites) { for (FavouritePoint fp : favourites) {
int number = 1; int number = 1;
String index; String index;

View file

@ -10,7 +10,7 @@ import android.util.Log;
import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener; import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.AndroidNetworkUtils.OnRequestsResultListener; import net.osmand.AndroidNetworkUtils.OnSendRequestsListener;
import net.osmand.AndroidNetworkUtils.RequestResponse; import net.osmand.AndroidNetworkUtils.RequestResponse;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
@ -608,9 +608,14 @@ public abstract class InAppPurchaseHelper {
addUserInfo(parameters); addUserInfo(parameters);
requests.add(new AndroidNetworkUtils.Request(url, parameters, userOperation, true, true)); requests.add(new AndroidNetworkUtils.Request(url, parameters, userOperation, true, true));
} }
AndroidNetworkUtils.sendRequestsAsync(ctx, requests, new OnRequestsResultListener() { AndroidNetworkUtils.sendRequestsAsync(ctx, requests, new OnSendRequestsListener() {
@Override @Override
public void onResult(@NonNull List<RequestResponse> results) { public void onRequestSent(@NonNull RequestResponse response) {
}
@Override
public void onRequestsSent(@NonNull List<RequestResponse> results) {
for (RequestResponse rr : results) { for (RequestResponse rr : results) {
String sku = rr.getRequest().getParameters().get("sku"); String sku = rr.getRequest().getParameters().get("sku");
PurchaseInfo info = getPurchaseInfo(sku); PurchaseInfo info = getPurchaseInfo(sku);

View file

@ -1172,6 +1172,9 @@ public class OsmandSettings {
public final OsmandPreference<String> BACKUP_ACCESS_TOKEN = new StringPreference(this, "backup_access_token", "").makeGlobal(); public final OsmandPreference<String> BACKUP_ACCESS_TOKEN = new StringPreference(this, "backup_access_token", "").makeGlobal();
public final OsmandPreference<String> BACKUP_ACCESS_TOKEN_UPDATE_TIME = new StringPreference(this, "backup_access_token_update_time", "").makeGlobal(); public final OsmandPreference<String> BACKUP_ACCESS_TOKEN_UPDATE_TIME = new StringPreference(this, "backup_access_token_update_time", "").makeGlobal();
public final OsmandPreference<Long> FAVORITES_LAST_UPLOADED_TIME = new LongPreference(this, "favorites_last_uploaded_time", 0L).makeGlobal();
public final OsmandPreference<Long> BACKUP_LAST_UPLOADED_TIME = new LongPreference(this, "backup_last_uploaded_time", 0L).makeGlobal();
// this value string is synchronized with settings_pref.xml preference name // this value string is synchronized with settings_pref.xml preference name
public final OsmandPreference<String> USER_OSM_BUG_NAME = public final OsmandPreference<String> USER_OSM_BUG_NAME =
new StringPreference(this, "user_osm_bug_name", "NoName/OsmAnd").makeGlobal().makeShared(); new StringPreference(this, "user_osm_bug_name", "NoName/OsmAnd").makeGlobal().makeShared();