diff --git a/.github/ISSUE_TEMPLATE/2-faq-report.md b/.github/ISSUE_TEMPLATE/2-faq-report.md index fbc18c4381..531d263d7f 100644 --- a/.github/ISSUE_TEMPLATE/2-faq-report.md +++ b/.github/ISSUE_TEMPLATE/2-faq-report.md @@ -1,8 +1,8 @@ --- name: "📚 Outdated FAQ" about: Report an issue in FAQ - --- + 🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 Please do not file FAQ issues on the GitHub issues tracker. diff --git a/.github/ISSUE_TEMPLATE/3-bug-report.md b/.github/ISSUE_TEMPLATE/3-bug-report.md index d12ea14578..0fd8c3e5ff 100644 --- a/.github/ISSUE_TEMPLATE/3-bug-report.md +++ b/.github/ISSUE_TEMPLATE/3-bug-report.md @@ -2,68 +2,17 @@ name: "\U0001F41E Bug report" about: Report a bug in OsmAnd --- - - -# 🐞 bug report - -### Is this a regression? - - - Yes, the previous version in which this bug was not present was: .... - ### Description - A clear and concise description of the problem... + +### How to reproduce? -## 🔬 Minimal Reproduction - - 1. Open app, and click on ... +### Your Environment +OsmAnd Version: +Android/iOS version: +Device model: -## 🔥 Exception or Error -

-
-
-
-
- - -## 🌍 Your Environment - -**OsmAnd Version:** -

-
-
-
-
- -**Device and Android/iOS version:** - -**Maps used (online or offline):** - -- [ ] Offline maps offered within the OsmAnd app for download. - -- [ ] Online (tile / raster) maps - - -**Anything else relevant?** +**Maps used (online or offline):** +If you have an issue related to offline maps, tell us the exact name of the map file where the issue occurs and its edition date. diff --git a/.github/ISSUE_TEMPLATE/4-routing-report.md b/.github/ISSUE_TEMPLATE/4-routing-report.md index 4c1e68c11a..216cd4b402 100644 --- a/.github/ISSUE_TEMPLATE/4-routing-report.md +++ b/.github/ISSUE_TEMPLATE/4-routing-report.md @@ -2,6 +2,7 @@ name: "\U0001F6A9 Routing report" about: Report a routing issue in OsmAnd --- + + - [ ] OsmAnd's in-app offline routing - [ ] Any online routing provider (YOURS, OpenRouteService, OSRM, etc.) ### Routing Profile + ### Start and end points @@ -38,6 +41,7 @@ Please give us the following information so that we can try to **reproduce** you Also, a permalink from [openstreetmap.org](https://www.openstreetmap.org/) can be helpful. --> ### Actual and expected routes + ### Is this a regression? @@ -45,9 +49,10 @@ Also, a permalink from [openstreetmap.org](https://www.openstreetmap.org/) can b Yes, the previous version in which this bug was not present was: .... -## 🌍 Your Environment +## 🌍 Your Environment **OsmAnd Version:** +

 
 
@@ -57,10 +62,11 @@ Also, a permalink from [openstreetmap.org](https://www.openstreetmap.org/) can b
 **Device and Android/iOS version:**
 
 **Maps used (online or offline):**
+
 
-- [ ] Offline maps offered within the OsmAnd app for download. 
-  
+
+- [ ] Offline maps offered within the OsmAnd app for download.
+
 - [ ] Online (tile / raster) maps 
 
-
 **Anything else relevant?**
diff --git a/.github/ISSUE_TEMPLATE/5-feature-request.md b/.github/ISSUE_TEMPLATE/5-feature-request.md
index 046fb3a90f..1315456d77 100644
--- a/.github/ISSUE_TEMPLATE/5-feature-request.md
+++ b/.github/ISSUE_TEMPLATE/5-feature-request.md
@@ -1,18 +1,18 @@
 ---
 name: "\U0001F680 Feature request"
 about: Suggest a feature for OsmAnd
-
 ---
+
  A clear and concise description of the problem or missing capability...
 
+
 
 ### Describe the solution you'd like
+
  If you have a solution in mind, please describe it.
 
-
 ### Describe alternatives you've considered
+
  Have you considered any alternative solutions or workarounds?
diff --git a/.gitignore b/.gitignore
index 5798e73f27..33e746a3d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,10 @@ OsmAndCore_*.aar
 .project
 out/
 
+# Huawei
+agconnect-services.json
+OsmAndHms.jks
+
 # Android Studio
 /.idea
 *.iml
diff --git a/GPX.md b/GPX.md
new file mode 100644
index 0000000000..b3f1e441e4
--- /dev/null
+++ b/GPX.md
@@ -0,0 +1,157 @@
+The OsmAnd's GPX file format conforms to the GPX 1.1 specification with additional data written as extensions. There are several sections of such data:
+
+## Track appearance
+The following parameters are used to customize the appearance of a track on the map. They are used inside the "gpx" tag and apply to all tracks contained in the gpx.
+#### Parameters
+* **show_arrows** [*true, false*] - show / hide arrows along the path line.
+* **width** [*thin, medium, bold, 1-24*] - width of the track line on the map. The thin, medium, and bold are style depended values (should be defined as currentTrackWidth attribute).
+* **color** [*#AARRGGBB, #RRGGBB*] - color of a track line on the map. Hex value.
+* **split_type** [*no_split, distance, time*] - split type for a track.
+* **split_interval** [*double*] - split interval for a track. Distance (meters), time (seconds).
+
+#### Example:
+```xml
+
+...
+  
+    true
+    #4e4eff
+    distance
+    2000.0
+    bold
+  
+
+```
+## Details of a track point (trkpt)
+Written to a gpx file while recording a track.
+* **speed** (meters per second)
+* **heading** (0-359 degrees)
+
+#### Example:
+```xml
+  
+    203
+    
+    3
+    
+      273
+      5.02
+    
+  
+```
+
+## Calculated route(s)
+This data contains all details of a route built with **OsmAnd** (route segments, turns, road names, road types, restrictions, etc.). The route can be completely restored as if just built, even in the absence of the respective offline maps.
+
+A gpx file may contain several routes. Each of them is contained in a specific segment under **trkseg** / **extensions**. A gpx file is saved in this form when exporting a constructed route or when saving a track that consists of several separate segments via the **Plan a route** functionality.
+**Plan a route** also adds one (or several, in accordance with the number of contained separate segments / tracks) **rte** blocks to the gpx file, containing route key points (**rtept**).
+#### Gpx structure:
+```xml
+
+  
+    
+    
+    
+    
+      
+      
+        
+      
+      
+      
+      
+        
+      
+    
+  
+
+
+
+
+  
+    
+    
+      
+      ...
+      
+      ...
+    
+  
+
+```
+
+#### Example:
+```xml
+
+  
+    Fri 06 Nov 2020
+  
+  
+    Fri 06 Nov 2020
+    
+      
+        0.801
+      
+      
+        0.998
+      
+      
+        1
+      
+      
+        0.963
+      
+      
+        0.899
+      
+
+      ....
+
+      
+        
+          
+          
+          
+          
+          ...
+        
+        
+          
+          
+          
+          
+          
+          ...
+        
+      
+    
+  
+
+  
+    
+      
+        pedestrian
+        0
+      
+    
+    
+      
+        pedestrian
+        24
+      
+    
+    
+      
+        pedestrian
+        89
+      
+    
+    
+      
+        pedestrian
+        121
+      
+    
+  
+
+```
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl
index 3edd8c94da..e25e4338de 100644
--- a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl
+++ b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl
@@ -20,6 +20,8 @@ import net.osmand.aidlapi.mapmarker.UpdateMapMarkerParams;
 
 import net.osmand.aidlapi.calculateroute.CalculateRouteParams;
 
+import net.osmand.aidlapi.profile.ExportProfileParams;
+
 import net.osmand.aidlapi.gpx.ImportGpxParams;
 import net.osmand.aidlapi.gpx.ShowGpxParams;
 import net.osmand.aidlapi.gpx.StartGpxRecordingParams;
@@ -103,6 +105,8 @@ import net.osmand.aidlapi.events.AKeyEventsParams;
 
 import net.osmand.aidlapi.info.AppInfoParams;
 
+import net.osmand.aidlapi.profile.ExportProfileParams;
+
 // NOTE: Add new methods at the end of file!!!
 
 interface IOsmAndAidlInterface {
@@ -867,4 +871,16 @@ interface IOsmAndAidlInterface {
     AppInfoParams getAppInfo();
 
     boolean setMapMargins(in MapMarginsParams params);
+
+    boolean exportProfile(in ExportProfileParams params);
+
+     /**
+     * Is any fragment open.
+     */
+    boolean isFragmentOpen();
+
+    /**
+    * Is contect menu open.
+    */
+    boolean isMenuOpen();
 }
\ No newline at end of file
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/OsmAndCustomizationConstants.java b/OsmAnd-api/src/net/osmand/aidlapi/OsmAndCustomizationConstants.java
index 2e1543321a..af48552bc4 100644
--- a/OsmAnd-api/src/net/osmand/aidlapi/OsmAndCustomizationConstants.java
+++ b/OsmAnd-api/src/net/osmand/aidlapi/OsmAndCustomizationConstants.java
@@ -4,6 +4,8 @@ public interface OsmAndCustomizationConstants {
 
 	// Navigation Drawer:
 	String DRAWER_ITEM_ID_SCHEME = "drawer.action.";
+	String DRAWER_SWITCH_PROFILE_ID = DRAWER_ITEM_ID_SCHEME + "switch_profile";
+	String DRAWER_CONFIGURE_PROFILE_ID = DRAWER_ITEM_ID_SCHEME + "configure_profile";
 	String DRAWER_DASHBOARD_ID = DRAWER_ITEM_ID_SCHEME + "dashboard";
 	String DRAWER_MAP_MARKERS_ID = DRAWER_ITEM_ID_SCHEME + "map_markers";
 	String DRAWER_MY_PLACES_ID = DRAWER_ITEM_ID_SCHEME + "my_places";
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/copyfile/CopyFileParams.java b/OsmAnd-api/src/net/osmand/aidlapi/copyfile/CopyFileParams.java
index 1118a17f5c..ad122f60c6 100644
--- a/OsmAnd-api/src/net/osmand/aidlapi/copyfile/CopyFileParams.java
+++ b/OsmAnd-api/src/net/osmand/aidlapi/copyfile/CopyFileParams.java
@@ -9,12 +9,21 @@ import net.osmand.aidlapi.AidlParams;
 
 public class CopyFileParams extends AidlParams {
 
+	public static final String DESTINATION_DIR_KEY = "destinationDir";
+	public static final String FILE_NAME_KEY = "fileName";
+	public static final String FILE_PART_DATA_KEY = "filePartData";
+	public static final String START_TIME_KEY = "startTime";
+	public static final String DONE_KEY = "done";
+	private String destinationDir;
 	private String fileName;
 	private byte[] filePartData;
 	private long startTime;
 	private boolean done;
 
-	public CopyFileParams(@NonNull String fileName, @NonNull byte[] filePartData, long startTime, boolean done) {
+	public CopyFileParams(@NonNull String destinationDir, @NonNull String fileName, @NonNull byte[] filePartData,
+	                      long startTime, boolean done) {
+
+		this.destinationDir = destinationDir;
 		this.fileName = fileName;
 		this.filePartData = filePartData;
 		this.startTime = startTime;
@@ -37,6 +46,10 @@ public class CopyFileParams extends AidlParams {
 		}
 	};
 
+	public String getDestinationDir() {
+		return destinationDir;
+	}
+
 	public String getFileName() {
 		return fileName;
 	}
@@ -55,23 +68,26 @@ public class CopyFileParams extends AidlParams {
 
 	@Override
 	public void writeToBundle(Bundle bundle) {
-		bundle.putString("fileName", fileName);
-		bundle.putByteArray("filePartData", filePartData);
-		bundle.putLong("startTime", startTime);
-		bundle.putBoolean("done", done);
+		bundle.putString(DESTINATION_DIR_KEY, destinationDir);
+		bundle.putString(FILE_NAME_KEY, fileName);
+		bundle.putByteArray(FILE_PART_DATA_KEY, filePartData);
+		bundle.putLong(START_TIME_KEY, startTime);
+		bundle.putBoolean(DONE_KEY, done);
 	}
 
 	@Override
 	protected void readFromBundle(Bundle bundle) {
-		fileName = bundle.getString("fileName");
-		filePartData = bundle.getByteArray("filePartData");
-		startTime = bundle.getLong("startTime");
-		done = bundle.getBoolean("done");
+		destinationDir = bundle.getString(DESTINATION_DIR_KEY);
+		fileName = bundle.getString(FILE_NAME_KEY);
+		filePartData = bundle.getByteArray(FILE_PART_DATA_KEY);
+		startTime = bundle.getLong(START_TIME_KEY);
+		done = bundle.getBoolean(DONE_KEY);
 	}
 
 	@Override
 	public String toString() {
 		return "CopyFileParams {" +
+				" destinationDir=" + destinationDir +
 				" fileName=" + fileName +
 				", filePartData size=" + filePartData.length +
 				", startTime=" + startTime +
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java b/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java
index 8b810a81b4..11ec2ce17f 100644
--- a/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java
+++ b/OsmAnd-api/src/net/osmand/aidlapi/customization/MapMarginsParams.java
@@ -3,18 +3,31 @@ package net.osmand.aidlapi.customization;
 import android.os.Bundle;
 import android.os.Parcel;
 
+import androidx.annotation.Nullable;
+
 import net.osmand.aidlapi.AidlParams;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class MapMarginsParams extends AidlParams {
 
-	private String appModeKey;
+	public static final String LEFT_MARGIN_KEY = "leftMargin";
+	public static final String TOP_MARGIN_KEY = "topMargin";
+	public static final String RIGHT_MARGIN_KEY = "rightMargin";
+	public static final String BOTTOM_MARGIN_KEY = "bottomMargin";
+	public static final String APP_MODES_KEYS_KEY = "appModesKeys";
+	private ArrayList appModesKeys = new ArrayList<>();
 	private int leftMargin;
 	private int topMargin;
 	private int rightMargin;
 	private int bottomMargin;
 
-	public MapMarginsParams(String appModeKey, int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
-		this.appModeKey = appModeKey;
+	public MapMarginsParams(int leftMargin, int topMargin, int rightMargin, int bottomMargin,
+	                        @Nullable List appModesKeys) {
+		if (appModesKeys != null) {
+			this.appModesKeys.addAll(appModesKeys);
+		}
 		this.leftMargin = leftMargin;
 		this.topMargin = topMargin;
 		this.rightMargin = rightMargin;
@@ -37,8 +50,8 @@ public class MapMarginsParams extends AidlParams {
 		}
 	};
 
-	public String getAppModeKey() {
-		return appModeKey;
+	public List getAppModesKeys() {
+		return appModesKeys;
 	}
 
 	public int getLeftMargin() {
@@ -59,19 +72,19 @@ public class MapMarginsParams extends AidlParams {
 
 	@Override
 	public void writeToBundle(Bundle bundle) {
-		bundle.putString("appModeKey", appModeKey);
-		bundle.putInt("leftMargin", leftMargin);
-		bundle.putInt("topMargin", topMargin);
-		bundle.putInt("rightMargin", rightMargin);
-		bundle.putInt("bottomMargin", bottomMargin);
+		bundle.putInt(LEFT_MARGIN_KEY, leftMargin);
+		bundle.putInt(TOP_MARGIN_KEY, topMargin);
+		bundle.putInt(RIGHT_MARGIN_KEY, rightMargin);
+		bundle.putInt(BOTTOM_MARGIN_KEY, bottomMargin);
+		bundle.putStringArrayList(APP_MODES_KEYS_KEY, appModesKeys);
 	}
 
 	@Override
 	protected void readFromBundle(Bundle bundle) {
-		appModeKey = bundle.getString("appModeKey");
-		leftMargin = bundle.getInt("leftMargin");
-		topMargin = bundle.getInt("topMargin");
-		rightMargin = bundle.getInt("rightMargin");
-		bottomMargin = bundle.getInt("bottomMargin");
+		leftMargin = bundle.getInt(LEFT_MARGIN_KEY);
+		topMargin = bundle.getInt(TOP_MARGIN_KEY);
+		rightMargin = bundle.getInt(RIGHT_MARGIN_KEY);
+		bottomMargin = bundle.getInt(BOTTOM_MARGIN_KEY);
+		appModesKeys = bundle.getStringArrayList(APP_MODES_KEYS_KEY);
 	}
 }
\ No newline at end of file
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/customization/ProfileSettingsParams.java b/OsmAnd-api/src/net/osmand/aidlapi/customization/ProfileSettingsParams.java
index 36959ef776..00c68851e7 100644
--- a/OsmAnd-api/src/net/osmand/aidlapi/customization/ProfileSettingsParams.java
+++ b/OsmAnd-api/src/net/osmand/aidlapi/customization/ProfileSettingsParams.java
@@ -5,15 +5,31 @@ import android.os.Bundle;
 import android.os.Parcel;
 
 import net.osmand.aidlapi.AidlParams;
+import net.osmand.aidlapi.profile.AExportSettingsType;
+
+import java.util.ArrayList;
+
+import static net.osmand.aidlapi.profile.ExportProfileParams.SETTINGS_TYPE_KEY;
 
 public class ProfileSettingsParams extends AidlParams {
 
+	public static final String VERSION_KEY = "version";
+	public static final String REPLACE_KEY = "replace";
+	public static final String LATEST_CHANGES_KEY = "latestChanges";
+	public static final String PROFILE_SETTINGS_URI_KEY = "profileSettingsUri";
 	private Uri profileSettingsUri;
 	private String latestChanges;
 	private int version;
+	private ArrayList settingsTypeKeyList = new ArrayList<>();
+	boolean replace;
 
-	public ProfileSettingsParams(Uri profileSettingsUri, String latestChanges, int version) {
+	public ProfileSettingsParams(Uri profileSettingsUri, ArrayList settingsTypeList, boolean replace,
+	                             String latestChanges, int version) {
 		this.profileSettingsUri = profileSettingsUri;
+		for (AExportSettingsType settingsType : settingsTypeList) {
+			settingsTypeKeyList.add(settingsType.name());
+		}
+		this.replace = replace;
 		this.latestChanges = latestChanges;
 		this.version = version;
 	}
@@ -46,17 +62,29 @@ public class ProfileSettingsParams extends AidlParams {
 		return profileSettingsUri;
 	}
 
+	public ArrayList getSettingsTypeKeys() {
+		return settingsTypeKeyList;
+	}
+
+	public boolean isReplace() {
+		return replace;
+	}
+
 	@Override
 	public void writeToBundle(Bundle bundle) {
-		bundle.putInt("version", version);
-		bundle.putString("latestChanges", latestChanges);
-		bundle.putParcelable("profileSettingsUri", profileSettingsUri);
+		bundle.putInt(VERSION_KEY, version);
+		bundle.putString(LATEST_CHANGES_KEY, latestChanges);
+		bundle.putParcelable(PROFILE_SETTINGS_URI_KEY, profileSettingsUri);
+		bundle.putStringArrayList(SETTINGS_TYPE_KEY, settingsTypeKeyList);
+		bundle.putBoolean(REPLACE_KEY, replace);
 	}
 
 	@Override
 	protected void readFromBundle(Bundle bundle) {
-		version = bundle.getInt("version");
-		latestChanges = bundle.getString("latestChanges");
-		profileSettingsUri = bundle.getParcelable("profileSettingsUri");
+		version = bundle.getInt(VERSION_KEY);
+		latestChanges = bundle.getString(LATEST_CHANGES_KEY);
+		profileSettingsUri = bundle.getParcelable(PROFILE_SETTINGS_URI_KEY);
+		settingsTypeKeyList = bundle.getStringArrayList(SETTINGS_TYPE_KEY);
+		replace = bundle.getBoolean(REPLACE_KEY);
 	}
 }
\ No newline at end of file
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.aidl b/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.aidl
new file mode 100644
index 0000000000..99c59bba2a
--- /dev/null
+++ b/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.profile;
+
+parcelable AExportSettingsType;
\ No newline at end of file
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.java b/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.java
new file mode 100644
index 0000000000..23c0189615
--- /dev/null
+++ b/OsmAnd-api/src/net/osmand/aidlapi/profile/AExportSettingsType.java
@@ -0,0 +1,11 @@
+package net.osmand.aidlapi.profile;
+
+public enum AExportSettingsType {
+	PROFILE,
+	QUICK_ACTIONS,
+	POI_TYPES,
+	MAP_SOURCES,
+	CUSTOM_RENDER_STYLE,
+	CUSTOM_ROUTING,
+	AVOID_ROADS;
+}
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.aidl b/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.aidl
new file mode 100644
index 0000000000..0dfefce6be
--- /dev/null
+++ b/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.aidl
@@ -0,0 +1,3 @@
+package net.osmand.aidlapi.profile;
+
+parcelable ExportProfileParams;
\ No newline at end of file
diff --git a/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.java b/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.java
new file mode 100644
index 0000000000..931f52eeb8
--- /dev/null
+++ b/OsmAnd-api/src/net/osmand/aidlapi/profile/ExportProfileParams.java
@@ -0,0 +1,61 @@
+package net.osmand.aidlapi.profile;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import net.osmand.aidlapi.AidlParams;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExportProfileParams extends AidlParams {
+
+	public static final String PROFILE_KEY = "profile";
+	public static final String SETTINGS_TYPE_KEY = "settings_type";
+	private String profile;
+	private ArrayList settingsTypeKeyList = new ArrayList<>();
+
+	public ExportProfileParams(String profile, ArrayList settingsTypeList) {
+
+		this.profile = profile;
+		for (AExportSettingsType settingsType : settingsTypeList) {
+			settingsTypeKeyList.add(settingsType.name());
+		}
+	}
+
+	public ExportProfileParams(Parcel in) {
+		readFromParcel(in);
+	}
+
+	public static final Creator CREATOR = new Creator() {
+		@Override
+		public ExportProfileParams createFromParcel(Parcel in) {
+			return new ExportProfileParams(in);
+		}
+
+		@Override
+		public ExportProfileParams[] newArray(int size) {
+			return new ExportProfileParams[size];
+		}
+	};
+
+	public String getProfile() {
+		return profile;
+	}
+
+	public List getSettingsTypeKeys() {
+		return settingsTypeKeyList;
+	}
+
+	@Override
+	public void writeToBundle(Bundle bundle) {
+		bundle.putString(PROFILE_KEY, profile);
+		bundle.putStringArrayList(SETTINGS_TYPE_KEY, settingsTypeKeyList);
+	}
+
+	@Override
+	protected void readFromBundle(Bundle bundle) {
+		profile = bundle.getString(PROFILE_KEY);
+		settingsTypeKeyList = bundle.getStringArrayList(SETTINGS_TYPE_KEY);
+	}
+}
\ No newline at end of file
diff --git a/OsmAnd-java/build.gradle b/OsmAnd-java/build.gradle
index 4385f9d0b0..2c5415a84f 100644
--- a/OsmAnd-java/build.gradle
+++ b/OsmAnd-java/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'java'
 apply plugin: 'maven-publish'
-    
+
 configurations {
 	android
 }
@@ -104,6 +104,9 @@ dependencies {
 	implementation 'com.moparisthebest:junidecode:0.1.1'
 	implementation 'com.vividsolutions:jts-core:1.14.0'
 	implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
+	implementation ('com.github.scribejava:scribejava-apis:7.1.1') {
+		exclude group: "com.fasterxml.jackson.core"
+	}
 	// turn off for now
 	//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
 	implementation 'net.sf.kxml:kxml2:2.1.8'
diff --git a/OsmAnd-java/src/main/java/com/jwetherell/openmap/common/MGRSPoint.java b/OsmAnd-java/src/main/java/com/jwetherell/openmap/common/MGRSPoint.java
index 8cbb306b7e..6e99eec8b5 100644
--- a/OsmAnd-java/src/main/java/com/jwetherell/openmap/common/MGRSPoint.java
+++ b/OsmAnd-java/src/main/java/com/jwetherell/openmap/common/MGRSPoint.java
@@ -14,6 +14,10 @@
 
 package com.jwetherell.openmap.common;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 public class MGRSPoint extends ZonedUTMPoint {
 
     /**
@@ -104,6 +108,15 @@ public class MGRSPoint extends ZonedUTMPoint {
      *            an UPPERCASE coordinate string is expected.
      */
     protected void decode(String mgrsString) throws NumberFormatException {
+        if (mgrsString.contains(" ")) {
+            String[] parts = mgrsString.split(" ");
+            StringBuilder s = new StringBuilder();
+            for (String i : parts) {
+                s.append(i);
+            }
+            mgrsString = s.toString();
+        }
+
         if (mgrsString == null || mgrsString.length() == 0) {
             throw new NumberFormatException("MGRSPoint coverting from nothing");
         }
@@ -633,6 +646,97 @@ public class MGRSPoint extends ZonedUTMPoint {
         return twoLetter;
     }
 
+    public String toFlavoredString() {
+        try {
+            List all = new ArrayList<>();
+            for (int i = 0; i <= mgrs.length(); i++) {
+                if (Character.isAlphabetic(mgrs.charAt(i))){
+                    all.add(mgrs.substring(0,i+1));
+                    all.add(mgrs.substring(i+1,i+3));
+                    String remains = mgrs.substring(i+3);
+                    all.add(remains.substring(0,remains.length()/2));
+                    all.add(remains.substring(remains.length()/2));
+                    break;
+                }
+            }
+            StringBuilder os = new StringBuilder();
+            for(String part: all){
+                if (os.length() > 0) os.append(" ");
+                os.append(part);
+            }
+            return os.toString();
+        }catch (Exception e){
+            return mgrs;
+        }
+    }
+
+    public String toFlavoredString(int accuracy) {
+        try {
+            List all = new ArrayList<>();
+            for (int i = 0; i <= mgrs.length(); i++) {
+                if (Character.isAlphabetic(mgrs.charAt(i))){
+                    all.add(mgrs.substring(0,i+1));
+                    all.add(mgrs.substring(i+1,i+3));
+                    String remains = mgrs.substring(i+3);
+                    int easting = Integer.parseInt(remains.substring(0,remains.length()/2));
+                    int northing = Integer.parseInt(remains.substring(remains.length()/2));
+                    double resolution = Math.pow(10, getAccuracy() - accuracy);
+                    long roundedEasting = Math.round(easting/resolution);
+                    long roundedNorthing = Math.round(northing/resolution);
+                    int eastShift = 0;
+                    int northShift = 0;
+                    if (roundedEasting == resolution*10){
+                        roundedEasting = 0L;
+                        eastShift = 1;
+                    }
+                    if (roundedNorthing == resolution*10){
+                        roundedNorthing = 0L;
+                        northShift = 1;
+                    }
+                    if (eastShift != 0 || northShift != 0){
+                        all.set(1, shiftChar(all.get(1), eastShift, northShift));
+                        String zero = "";
+                    }
+
+
+                    all.add(String.format("%0" + accuracy + "d", roundedEasting));
+                    all.add(String.format("%0" + accuracy + "d", roundedNorthing));
+                    break;
+                }
+            }
+            StringBuilder os = new StringBuilder();
+            for(String part: all){
+                if (os.length() > 0) os.append(" ");
+                os.append(part);
+            }
+            return os.toString();
+        }catch (Exception e){
+            return toFlavoredString();
+        }
+    }
+
+    private static String shiftChar(String chars, int east, int north){
+        ArrayList keys = new ArrayList(
+                Arrays.asList('A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z'));
+        StringBuilder s = new StringBuilder();
+        if (east != 0){
+            int idx = keys.indexOf(chars.charAt(0));
+            idx += east;
+            if (idx >= keys.size()) idx -= keys.size();
+            if (idx < 0) idx += keys.size();
+            s.append(keys.get(idx));
+        }else s.append(chars.charAt(0));
+        if (north != 0){
+            int idx = keys.indexOf(chars.charAt(1));
+            idx += north;
+            if (idx >= keys.size()) idx -= keys.size();
+            if (idx < 0) idx += keys.size();
+            s.append(keys.get(idx));
+        }else s.append(chars.charAt(1));
+        return s.toString();
+    }
+
+
     /**
      * {@inheritDoc}
      */
diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java
index 23afd7dd2a..1d1b1da020 100644
--- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java
+++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java
@@ -52,9 +52,10 @@ public class GPXUtilities {
 	private static final String DEFAULT_ICON_NAME = "special_star";
 	private static final String BACKGROUND_TYPE_EXTENSION = "background";
 	private static final String PROFILE_TYPE_EXTENSION = "profile";
+	private static final String GAP_PROFILE_TYPE = "gap";
 	private static final String TRKPT_INDEX_EXTENSION = "trkpt_idx";
 
-	private final static String GPX_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; //$NON-NLS-1$
+	public final static String GPX_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; //$NON-NLS-1$
 	private final static String GPX_TIME_FORMAT_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; //$NON-NLS-1$
 
 	private final static NumberFormat latLonFormat = new DecimalFormat("0.00#####", new DecimalFormatSymbols(
@@ -70,6 +71,7 @@ public class GPXUtilities {
 		WHITE(0xFFFFFFFF),
 		RED(0xFFFF0000),
 		GREEN(0xFF00FF00),
+		DARKGREEN(0xFF006400),
 		BLUE(0xFF0000FF),
 		YELLOW(0xFFFFFF00),
 		CYAN(0xFF00FFFF),
@@ -324,6 +326,20 @@ public class GPXUtilities {
 			getExtensionsToWrite().put(PROFILE_TYPE_EXTENSION, profileType);
 		}
 
+		public boolean hasProfile() {
+			String profileType = getProfileType();
+			return profileType != null && !GAP_PROFILE_TYPE.equals(profileType);
+		}
+
+		public boolean isGap() {
+			String profileType = getProfileType();
+			return GAP_PROFILE_TYPE.equals(profileType);
+		}
+
+		public void setGap() {
+			setProfileType(GAP_PROFILE_TYPE);
+		}
+
 		public void removeProfileType() {
 			getExtensionsToWrite().remove(PROFILE_TYPE_EXTENSION);
 		}
@@ -374,11 +390,16 @@ public class GPXUtilities {
 
 	public static class TrkSegment extends GPXExtensions {
 		public boolean generalSegment = false;
-
 		public List points = new ArrayList<>();
 
 		public Object renderer;
 
+		public List routeSegments = new ArrayList<>();
+		public List routeTypes = new ArrayList<>();
+
+		public boolean hasRoute() {
+			return !routeSegments.isEmpty() && !routeTypes.isEmpty();
+		}
 
 		public List splitByDistance(double meters, boolean joinSegments) {
 			return split(getDistanceMetric(), getTimeSplit(), meters, joinSegments);
@@ -393,7 +414,6 @@ public class GPXUtilities {
 			splitSegment(metric, secondaryMetric, metricLimit, splitSegments, this, joinSegments);
 			return convert(splitSegments);
 		}
-
 	}
 
 	public static class Track extends GPXExtensions {
@@ -1078,9 +1098,6 @@ public class GPXUtilities {
 		private List points = new ArrayList<>();
 		public List routes = new ArrayList<>();
 
-		public List routeSegments = new ArrayList<>();
-		public List routeTypes = new ArrayList<>();
-
 		public Exception error = null;
 		public String path = "";
 		public boolean showCurrentTrack;
@@ -1108,7 +1125,7 @@ public class GPXUtilities {
 		}
 
 		public boolean hasRoute() {
-			return !routeSegments.isEmpty() && !routeTypes.isEmpty();
+			return getNonEmptyTrkSegments(true).size() > 0;
 		}
 
 		public List getPoints() {
@@ -1218,7 +1235,7 @@ public class GPXUtilities {
 			GPXTrackAnalysis g = new GPXTrackAnalysis();
 			g.wptPoints = points.size();
 			g.wptCategoryNames = getWaypointCategories(true);
-			List splitSegments = new ArrayList();
+			List splitSegments = new ArrayList<>();
 			for (int i = 0; i < tracks.size(); i++) {
 				Track subtrack = tracks.get(i);
 				for (TrkSegment segment : subtrack.segments) {
@@ -1243,6 +1260,15 @@ public class GPXUtilities {
 			return points;
 		}
 
+		public List getRoutePoints(int routeIndex) {
+			List points = new ArrayList<>();
+			if (routes.size() > routeIndex) {
+				Route rt = routes.get(routeIndex);
+				points.addAll(rt.points);
+			}
+			return points;
+		}
+
 		public boolean hasRtePt() {
 			for (Route r : routes) {
 				if (r.points.size() > 0) {
@@ -1318,15 +1344,16 @@ public class GPXUtilities {
 			return pt;
 		}
 
-		public TrkSegment getNonEmptyTrkSegment() {
-			for (GPXUtilities.Track t : tracks) {
+		public List getNonEmptyTrkSegments(boolean routesOnly) {
+			List segments = new ArrayList<>();
+			for (Track t : tracks) {
 				for (TrkSegment s : t.segments) {
-					if (s.points.size() > 0) {
-						return s;
+					if (!s.generalSegment && s.points.size() > 0 && (!routesOnly || s.hasRoute())) {
+						segments.add(s);
 					}
 				}
 			}
-			return null;
+			return segments;
 		}
 
 		public void addTrkSegment(List points) {
@@ -1365,8 +1392,8 @@ public class GPXUtilities {
 			return false;
 		}
 
-		public void addRoutePoints(List points) {
-			if (routes.size() == 0) {
+		public void addRoutePoints(List points, boolean addRoute) {
+			if (routes.size() == 0 || addRoute) {
 				Route route = new Route();
 				routes.add(route);
 			}
@@ -1608,7 +1635,7 @@ public class GPXUtilities {
 					bottom = Math.min(bottom, p.getLatitude());
 				}
 			}
-			for (GPXUtilities.Route route : routes) {
+			for (Route route : routes) {
 				for (WptPt p : route.points) {
 					if (left == 0 && right == 0) {
 						left = p.getLongitude();
@@ -1720,7 +1747,7 @@ public class GPXUtilities {
 
 	public static String asString(GPXFile file) {
 		final Writer writer = new StringWriter();
-		GPXUtilities.writeGpx(writer, file);
+		writeGpx(writer, file);
 		return writer.toString();
 	}
 
@@ -1807,6 +1834,8 @@ public class GPXUtilities {
 							writeWpt(format, serializer, p);
 							serializer.endTag(null, "trkpt"); //$NON-NLS-1$
 						}
+						assignRouteExtensionWriter(segment);
+						writeExtensions(serializer, segment);
 						serializer.endTag(null, "trkseg"); //$NON-NLS-1$
 					}
 					writeExtensions(serializer, track);
@@ -1834,7 +1863,6 @@ public class GPXUtilities {
 				serializer.endTag(null, "wpt"); //$NON-NLS-1$
 			}
 
-			assignRouteExtensionWriter(file);
 			writeExtensions(serializer, file);
 
 			serializer.endTag(null, "gpx"); //$NON-NLS-1$
@@ -1847,19 +1875,19 @@ public class GPXUtilities {
 		return null;
 	}
 
-	private static void assignRouteExtensionWriter(final GPXFile gpxFile) {
-		if (gpxFile.hasRoute() && gpxFile.getExtensionsWriter() == null) {
-			gpxFile.setExtensionsWriter(new GPXExtensionsWriter() {
+	private static void assignRouteExtensionWriter(final TrkSegment segment) {
+		if (segment.hasRoute() && segment.getExtensionsWriter() == null) {
+			segment.setExtensionsWriter(new GPXExtensionsWriter() {
 				@Override
 				public void writeExtensions(XmlSerializer serializer) {
 					StringBundle bundle = new StringBundle();
 					List segmentsBundle = new ArrayList<>();
-					for (RouteSegment segment : gpxFile.routeSegments) {
+					for (RouteSegment segment : segment.routeSegments) {
 						segmentsBundle.add(segment.toStringBundle());
 					}
 					bundle.putBundleList("route", "segment", segmentsBundle);
 					List typesBundle = new ArrayList<>();
-					for (RouteType routeType : gpxFile.routeTypes) {
+					for (RouteType routeType : segment.routeTypes) {
 						typesBundle.add(routeType.toStringBundle());
 					}
 					bundle.putBundleList("types", "type", typesBundle);
@@ -1901,12 +1929,15 @@ public class GPXUtilities {
 	}
 
 	private static void writeExtensions(XmlSerializer serializer, GPXExtensions p) throws IOException {
-		Map extensionsToRead = p.getExtensionsToRead();
+		writeExtensions(serializer, p.getExtensionsToRead(), p);
+	}
+
+	private static void writeExtensions(XmlSerializer serializer, Map extensions, GPXExtensions p) throws IOException {
 		GPXExtensionsWriter extensionsWriter = p.getExtensionsWriter();
-		if (!extensionsToRead.isEmpty() || extensionsWriter != null) {
+		if (!extensions.isEmpty() || extensionsWriter != null) {
 			serializer.startTag(null, "extensions");
-			if (!extensionsToRead.isEmpty()) {
-				for (Entry s : extensionsToRead.entrySet()) {
+			if (!extensions.isEmpty()) {
+				for (Entry s : extensions.entrySet()) {
 					writeNotNullText(serializer, s.getKey(), s.getValue());
 				}
 			}
@@ -1943,7 +1974,20 @@ public class GPXUtilities {
 		if (!Float.isNaN(p.heading)) {
 			p.getExtensionsToWrite().put("heading", String.valueOf(Math.round(p.heading)));
 		}
-		writeExtensions(serializer, p);
+		Map extensions = p.getExtensionsToRead();
+		if (!"rtept".equals(serializer.getName())) {
+			// Leave "profile" and "trkpt" tags for rtept only
+			extensions.remove(PROFILE_TYPE_EXTENSION);
+			extensions.remove(TRKPT_INDEX_EXTENSION);
+			writeExtensions(serializer, extensions, p);
+		} else {
+			// Remove "gap" profile
+			String profile = extensions.get(PROFILE_TYPE_EXTENSION);
+			if (GAP_PROFILE_TYPE.equals(profile)) {
+				extensions.remove(PROFILE_TYPE_EXTENSION);
+			}
+			writeExtensions(serializer, p);
+		}
 	}
 
 	private static void writeAuthor(XmlSerializer serializer, Author author) throws IOException {
@@ -2099,10 +2143,11 @@ public class GPXUtilities {
 			TrkSegment routeTrackSegment = new TrkSegment();
 			routeTrack.segments.add(routeTrackSegment);
 			Stack parserState = new Stack<>();
+			TrkSegment firstSegment = null;
 			boolean extensionReadMode = false;
 			boolean routePointExtension = false;
-			List routeSegments = gpxFile.routeSegments;
-			List routeTypes = gpxFile.routeTypes;
+			List routeSegments = new ArrayList<>();
+			List routeTypes = new ArrayList<>();
 			boolean routeExtension = false;
 			boolean typesExtension = false;
 			parserState.push(gpxFile);
@@ -2403,6 +2448,16 @@ public class GPXUtilities {
 						assert pop instanceof Route;
 					} else if (tag.equals("trkseg")) {
 						Object pop = parserState.pop();
+						if (pop instanceof TrkSegment) {
+							TrkSegment segment = (TrkSegment) pop;
+							segment.routeSegments = routeSegments;
+							segment.routeTypes = routeTypes;
+							routeSegments = new ArrayList<>();
+							routeTypes = new ArrayList<>();
+							if (firstSegment == null) {
+								firstSegment = segment;
+							}
+						}
 						assert pop instanceof TrkSegment;
 					} else if (tag.equals("rpt")) {
 						Object pop = parserState.pop();
@@ -2413,6 +2468,10 @@ public class GPXUtilities {
 			if (!routeTrackSegment.points.isEmpty()) {
 				gpxFile.tracks.add(routeTrack);
 			}
+			if (!routeSegments.isEmpty() && !routeTypes.isEmpty() && firstSegment != null) {
+				firstSegment.routeSegments = routeSegments;
+				firstSegment.routeTypes = routeTypes;
+			}
 		} catch (Exception e) {
 			gpxFile.error = e;
 			log.error("Error reading gpx", e); //$NON-NLS-1$
diff --git a/OsmAnd-java/src/main/java/net/osmand/IProgress.java b/OsmAnd-java/src/main/java/net/osmand/IProgress.java
index 762dab727b..407cd735f3 100644
--- a/OsmAnd-java/src/main/java/net/osmand/IProgress.java
+++ b/OsmAnd-java/src/main/java/net/osmand/IProgress.java
@@ -45,7 +45,7 @@ public interface IProgress {
 		public boolean isInterrupted() {return false;}
 		
 		@Override
-		public boolean isIndeterminate() {return false;}
+		public boolean isIndeterminate() {return true;}
 		
 		@Override
 		public void finishTask() {}
diff --git a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
index 0dea2d42fb..3e4cd5af55 100644
--- a/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
+++ b/OsmAnd-java/src/main/java/net/osmand/IndexConstants.java
@@ -14,7 +14,8 @@ public class IndexConstants {
 	public static final String TEMP_SOURCE_TO_LOAD = "temp";
 	
 	public static final String POI_INDEX_EXT = ".poi.odb"; //$NON-NLS-1$
-	
+
+	public static final String ZIP_EXT = ".zip"; //$NON-NLS-1$
 	public static final String BINARY_MAP_INDEX_EXT = ".obf"; //$NON-NLS-1$
 	public static final String BINARY_MAP_INDEX_EXT_ZIP = ".obf.zip"; //$NON-NLS-1$
 	
@@ -45,6 +46,9 @@ public class IndexConstants {
 
 	public static final String GPX_FILE_EXT = ".gpx"; //$NON-NLS-1$
 
+	public static final String WPT_CHART_FILE_EXT = ".wpt.chart";
+	public static final String SQLITE_CHART_FILE_EXT = ".3d.chart";
+
 	public final static String POI_TABLE = "poi"; //$NON-NLS-1$
 	
 	public static final String INDEX_DOWNLOAD_DOMAIN = "download.osmand.net";
@@ -68,9 +72,11 @@ public class IndexConstants {
 	public static final String FONT_INDEX_DIR = "fonts/"; //$NON-NLS-1$
 	public static final String VOICE_INDEX_DIR = "voice/"; //$NON-NLS-1$
 	public static final String RENDERERS_DIR = "rendering/"; //$NON-NLS-1$
-	public static final String ROUTING_XML_FILE= "routing.xml";
+	public static final String ROUTING_XML_FILE = "routing.xml";
 	public static final String SETTINGS_DIR = "settings/"; //$NON-NLS-1$
 	public static final String TEMP_DIR = "temp/";
 	public static final String ROUTING_PROFILES_DIR = "routing/";
 	public static final String PLUGINS_DIR = "plugins/";
+
+	public static final String VOICE_PROVIDER_SUFFIX = "-tts";
 }
diff --git a/OsmAnd-java/src/main/java/net/osmand/LocationConvert.java b/OsmAnd-java/src/main/java/net/osmand/LocationConvert.java
index 0f4ec96883..88eb471840 100644
--- a/OsmAnd-java/src/main/java/net/osmand/LocationConvert.java
+++ b/OsmAnd-java/src/main/java/net/osmand/LocationConvert.java
@@ -15,6 +15,7 @@ public class LocationConvert {
 	public static final int FORMAT_SECONDS = 2;
 	public static final int UTM_FORMAT = 3;
 	public static final int OLC_FORMAT = 4;
+	public static final int MGRS_FORMAT = 5;
 	private static final char DELIM = ':';
 	private static final char DELIMITER_DEGREES = '°';
 	private static final char DELIMITER_MINUTES = '′';
diff --git a/OsmAnd-java/src/main/java/net/osmand/TspAnt.java b/OsmAnd-java/src/main/java/net/osmand/TspAnt.java
index 815bddb7ef..6964a3facd 100644
--- a/OsmAnd-java/src/main/java/net/osmand/TspAnt.java
+++ b/OsmAnd-java/src/main/java/net/osmand/TspAnt.java
@@ -106,9 +106,11 @@ public class TspAnt {
     // Allocates all memory.
     // Adds 1 to edge lengths to ensure no zero length edges.
     public TspAnt readGraph(List intermediates, LatLon start, LatLon end) {
-        boolean keepEndPoint = end != null;
-    	List l = new ArrayList();
-    	l.add(start);
+		boolean keepEndPoint = end != null;
+		List l = new ArrayList();
+		if (start != null) {
+			l.add(start);
+		}
     	l.addAll(intermediates);
         if (keepEndPoint) {
             l.add(end);
diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java
index 857eb4f4d3..cb348cf9b4 100644
--- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java
+++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapPoiReaderAdapter.java
@@ -11,8 +11,6 @@ import java.util.Arrays;
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 
 import net.osmand.Collator;
 import net.osmand.CollatorStringMatcher;
@@ -576,13 +574,12 @@ public class BinaryMapPoiReaderAdapter {
 							}
 						}
 						if (!matches) {
-							Map lt = am.getAdditionalInfo();
-							for (Entry e : lt.entrySet()) {
-								if(!e.getKey().contains("_name") && 
-									!e.getKey().equals("brand")) {
+							for (String key : am.getAdditionalInfoKeys()) {
+								if(!key.contains("_name") && 
+									!key.equals("brand")) {
 									continue;
 								}
-								matches = matcher.matches(e.getValue());
+								matches = matcher.matches(am.getAdditionalInfo(key));
 								if (matches) {
 									break;
 								}
@@ -812,7 +809,6 @@ public class BinaryMapPoiReaderAdapter {
 	}
 
 	private boolean checkCategories(SearchRequest req, PoiRegion region) throws IOException {
-		StringBuilder subType = new StringBuilder();
 		while (true) {
 			int t = codedIS.readTag();
 			int tag = WireFormat.getTagFieldNumber(t);
diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/GeocodingUtilities.java b/OsmAnd-java/src/main/java/net/osmand/binary/GeocodingUtilities.java
index 77357bac9b..a372f12aa1 100644
--- a/OsmAnd-java/src/main/java/net/osmand/binary/GeocodingUtilities.java
+++ b/OsmAnd-java/src/main/java/net/osmand/binary/GeocodingUtilities.java
@@ -334,14 +334,16 @@ public class GeocodingUtilities {
 		boolean eqStreet = Algorithms.stringsEqual(gr1.streetName, gr2.streetName);
 		if (eqStreet) {
 			boolean sameObj = false;
-			if (gr1.building != null && gr2.building != null) {
-				if (Algorithms.stringsEqual(gr1.building.getName(), gr2.building.getName())) {
-					// same building
+			if (gr1.city != null && gr2.city != null) {
+				if (gr1.building != null && gr2.building != null) {
+					if (Algorithms.stringsEqual(gr1.building.getName(), gr2.building.getName())) {
+						// same building
+						sameObj = true;
+					}
+				} else if (gr1.building == null && gr2.building == null) {
+					// same street
 					sameObj = true;
 				}
-			} else if (gr1.building == null && gr2.building == null) {
-				// same street
-				sameObj = true;
 			}
 			if (sameObj) {
 				double cityDist1 = MapUtils.getDistance(gr1.searchPoint, gr1.city.getLocation());
diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
index a5c68f1938..4af1596c2c 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
@@ -1,17 +1,7 @@
 package net.osmand.data;
 
-import net.osmand.Location;
-import net.osmand.osm.MapPoiTypes;
-import net.osmand.osm.PoiCategory;
-import net.osmand.util.Algorithms;
-
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -21,9 +11,14 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.zip.GZIPInputStream;
+
+import org.json.JSONObject;
 
 import gnu.trove.list.array.TIntArrayList;
+import net.osmand.Location;
+import net.osmand.osm.MapPoiTypes;
+import net.osmand.osm.PoiCategory;
+import net.osmand.util.Algorithms;
 
 
 public class Amenity extends MapObject {
@@ -96,12 +91,46 @@ public class Amenity extends MapObject {
 	}
 
 
-	public Map getAdditionalInfo() {
+	// this method should be used carefully
+	public Map getInternalAdditionalInfoMap() {
 		if (additionalInfo == null) {
 			return Collections.emptyMap();
 		}
 		return additionalInfo;
 	}
+	
+	public Collection getAdditionalInfoValues(boolean excludeZipped) {
+		if (additionalInfo == null) {
+			return Collections.emptyList();
+		}
+		boolean zipped = false;
+		for(String v : additionalInfo.values()) {
+			if(isContentZipped(v)) {
+				zipped = true;
+				break;
+			}
+		}
+		if(zipped) {
+			List r = new ArrayList<>(additionalInfo.size());
+			for(String str : additionalInfo.values()) {
+				if(excludeZipped && isContentZipped(str)) {
+					
+				} else {
+					r.add(unzipContent(str));
+				}
+			}
+			return r;
+		} else {
+			return additionalInfo.values();
+		}
+	}
+	
+	public Collection getAdditionalInfoKeys() {
+		if (additionalInfo == null) {
+			return Collections.emptyList();
+		}
+		return additionalInfo.keySet();
+	}
 
 	public void setAdditionalInfo(Map additionalInfo) {
 		this.additionalInfo = null;
@@ -134,7 +163,7 @@ public class Amenity extends MapObject {
 			}
 			this.additionalInfo.put(tag, value);
 			if (OPENING_HOURS.equals(tag)) {
-				this.openingHours = value;
+				this.openingHours = unzipContent(value);
 			}
 		}
 	}
@@ -182,7 +211,7 @@ public class Amenity extends MapObject {
 		}
 		int maxLen = 0;
 		String lng = defLang;
-		for (String nm : getAdditionalInfo().keySet()) {
+		for (String nm : getAdditionalInfoKeys()) {
 			if (nm.startsWith(tag + ":")) {
 				String key = nm.substring(tag.length() + 1);
 				String cnt = getAdditionalInfo(tag + ":" + key);
@@ -204,7 +233,7 @@ public class Amenity extends MapObject {
 
 	public List getNames(String tag, String defTag) {
 		List l = new ArrayList();
-		for (String nm : getAdditionalInfo().keySet()) {
+		for (String nm : getAdditionalInfoKeys()) {
 			if (nm.startsWith(tag + ":")) {
 				l.add(nm.substring(tag.length() + 1));
 			} else if (nm.equals(tag)) {
@@ -229,7 +258,7 @@ public class Amenity extends MapObject {
 		if (!Algorithms.isEmpty(enName)) {
 			return enName;
 		}
-		for (String nm : getAdditionalInfo().keySet()) {
+		for (String nm : getAdditionalInfoKeys()) {
 			if (nm.startsWith(tag + ":")) {
 				return getAdditionalInfo(nm);
 			}
@@ -345,4 +374,6 @@ public class Amenity extends MapObject {
 		}
 		return a;
 	}
+
+	
 }
diff --git a/OsmAnd-java/src/main/java/net/osmand/data/City.java b/OsmAnd-java/src/main/java/net/osmand/data/City.java
index f8faef5f67..a29dc3b741 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/City.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/City.java
@@ -24,6 +24,10 @@ public class City extends MapObject {
 		public double getRadius() {
 			return radius;
 		}
+		
+		public boolean storedAsSeparateAdminEntity() {
+			return this != DISTRICT && this != NEIGHBOURHOOD && this != BOROUGH;
+		}
 
 		public static String valueToString(CityType t) {
 			return t.toString().toLowerCase();
diff --git a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java
index cb121cedfc..d3700896b2 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java
@@ -13,7 +13,6 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -351,8 +350,8 @@ public abstract class MapObject implements Comparable {
 		return json;
 	}
 	
-	public String unzipContent(String str) {
-		if (str != null && str.startsWith(" gz ")) {
+	String unzipContent(String str) {
+		if (isContentZipped(str)) {
 			try {
 				int ind = 4;
 				byte[] bytes = new byte[str.length() - ind];
@@ -369,6 +368,10 @@ public abstract class MapObject implements Comparable {
 				}
 				br.close();
 				str = bld.toString();
+				// ugly fix of temporary problem of map generation
+				if(isContentZipped(str)) {
+					str = unzipContent(str);
+				}
 			} catch (IOException e) {
 				e.printStackTrace();
 			}
@@ -376,6 +379,10 @@ public abstract class MapObject implements Comparable {
 		return str;
 	}
 
+	boolean isContentZipped(String str) {
+		return str != null && str.startsWith(" gz ");
+	}
+
 	protected static void parseJSON(JSONObject json, MapObject o) {
 		if (json.has("name")) {
 			o.name = json.getString("name");
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java
index f535af8610..e273fa4e7f 100644
--- a/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java
@@ -823,7 +823,7 @@ public class MapPoiTypes {
 		}
 		String name = keyName;
 		name = name.replace('_', ' ');
-		return Algorithms.capitalizeFirstLetterAndLowercase(name);
+		return Algorithms.capitalizeFirstLetter(name);
 	}
 
 	public boolean isRegisteredType(PoiCategory t) {
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/MapRenderingTypes.java b/OsmAnd-java/src/main/java/net/osmand/osm/MapRenderingTypes.java
index 34e5048930..d996678e44 100644
--- a/OsmAnd-java/src/main/java/net/osmand/osm/MapRenderingTypes.java
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/MapRenderingTypes.java
@@ -27,7 +27,7 @@ public abstract class MapRenderingTypes {
 
 	private static final Log log = PlatformUtil.getLog(MapRenderingTypes.class);
 	public static final String[] langs = new String[] { "af", "als", "ar", "az", "be", "bg", "bn", "bpy", "br", "bs", "ca", "ceb", "cs", "cy", "da", "de", "el", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gl", "he", "hi", "hsb",
-		"hr", "ht", "hu", "hy", "id", "is", "it", "ja", "ka", "ko", "ku", "la", "lb", "lo", "lt", "lv", "mk", "ml", "mr", "ms", "nds", "new", "nl", "nn", "no", "nv", "os", "pl", "pms", "pt", "ro", "ru", "sc", "sh", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "vi", "vo", "zh", "zh-hans", "zh-hant",  };
+		"hr", "ht", "hu", "hy", "id", "is", "it", "ja", "ka", "kn", "ko", "ku", "la", "lb", "lo", "lt", "lv", "mk", "ml", "mr", "ms", "nds", "new", "nl", "nn", "no", "nv", "os", "pl", "pms", "pt", "ro", "ru", "sc", "sh", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "vi", "vo", "zh", "zh-hans", "zh-hant",  };
 	
 	
 	public final static byte RESTRICTION_NO_RIGHT_TURN = 1;
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/edit/Entity.java b/OsmAnd-java/src/main/java/net/osmand/osm/edit/Entity.java
index 6005f85035..7bb8edbf49 100644
--- a/OsmAnd-java/src/main/java/net/osmand/osm/edit/Entity.java
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/edit/Entity.java
@@ -114,6 +114,8 @@ public abstract class Entity implements Serializable {
 	public static final int MODIFY_DELETED = -1;
 	public static final int MODIFY_MODIFIED = 1;
 	public static final int MODIFY_CREATED = 2;
+	public static final String POI_TYPE_TAG = "poi_type_tag";
+	public static final String REMOVE_TAG_PREFIX = "----";
 
 	public Entity(long id) {
 		this.id = id;
@@ -241,6 +243,11 @@ public abstract class Entity implements Serializable {
 		return Collections.unmodifiableMap(tags);
 	}
 
+	public boolean isNotValid(String tag) {
+		String val = getTag(tag);
+		return val == null || val.length() == 0 || tag.length() == 0
+				|| tag.startsWith(REMOVE_TAG_PREFIX) || tag.equals(POI_TYPE_TAG);
+	}
 
 	public Collection getTagKeySet() {
 		if (tags == null) {
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java
index 282d6addbd..28a1ec3fb9 100644
--- a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java
@@ -1,30 +1,22 @@
 package net.osmand.osm.io;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.Map;
-import java.util.zip.GZIPOutputStream;
-
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
 import net.osmand.PlatformUtil;
+import net.osmand.osm.oauth.OsmOAuthAuthorizationClient;
 import net.osmand.util.Algorithms;
-
 import org.apache.commons.logging.Log;
 
+import java.io.*;
+import java.net.*;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.GZIPOutputStream;
+
 public class NetworkUtils {
 	private static final Log log = PlatformUtil.getLog(NetworkUtils.class);
-
+	private static final String GPX_UPLOAD_USER_AGENT = "OsmGPXUploadAgent";
 	private static Proxy proxy = null;
 
 	public static String sendGetRequest(String urlText, String userNamePassword, StringBuilder responseBody){
@@ -55,7 +47,6 @@ public class NetworkUtils {
 						responseBody.append("\n"); //$NON-NLS-1$
 					}
 					responseBody.append(s);
-					
 				}
 				is.close();
 			}
@@ -65,9 +56,10 @@ public class NetworkUtils {
 			return e.getMessage();
 		}
 	}
-	
 	private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$
-	public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, String formName, boolean gzip, Map additionalMapData){
+	public static String uploadFile(String urlText, File fileToUpload, String userNamePassword,
+									OsmOAuthAuthorizationClient client,
+									String formName, boolean gzip, Map additionalMapData){
 		URL url;
 		try {
 			boolean firstPrm =!urlText.contains("?");
@@ -77,34 +69,48 @@ public class NetworkUtils {
 			}
 			log.info("Start uploading file to " + urlText + " " +fileToUpload.getName());
 			url = new URL(urlText);
-			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-			conn.setDoInput(true);
-			conn.setDoOutput(true);
-			conn.setRequestMethod("POST");
-			if(userNamePassword != null) {
-				conn.setRequestProperty("Authorization", "Basic " + Base64.encode(userNamePassword)); //$NON-NLS-1$ //$NON-NLS-2$
+			HttpURLConnection conn;
+			if (client != null && client.isValidToken()){
+				OAuthRequest req = new OAuthRequest(Verb.POST, urlText);
+				client.getService().signRequest(client.getAccessToken(), req);
+				req.addHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+				try {
+					Response r = client.getHttpClient().execute(GPX_UPLOAD_USER_AGENT, req.getHeaders(), req.getVerb(),
+							req.getCompleteUrl(), fileToUpload);
+					if (r.getCode() != 200) {
+						return r.getBody();
+					}
+					return null;
+				} catch (InterruptedException e) {
+					log.error(e);
+				} catch (ExecutionException e) {
+					log.error(e);
+				}
+				return null;
+			}
+			else {
+				conn = (HttpURLConnection) url.openConnection();
+				conn.setDoInput(true);
+				conn.setDoOutput(true);
+				conn.setRequestMethod("POST");
+				if(userNamePassword != null) {
+					conn.setRequestProperty("Authorization", "Basic " + Base64.encode(userNamePassword)); //$NON-NLS-1$ //$NON-NLS-2$
+				}
 			}
-			
 	        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); //$NON-NLS-1$ //$NON-NLS-2$
 	        conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$
-
 	        OutputStream ous = conn.getOutputStream();
-//			for (String key : additionalMapData.keySet()) {
-//				ous.write(("--" + BOUNDARY + "\r\n").getBytes());
-//				ous.write(("content-disposition: form-data; name=\"" + key + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
-//				ous.write((additionalMapData.get(key) + "\r\n").getBytes());
-//			}
-			ous.write(("--" + BOUNDARY+"\r\n").getBytes());
+			ous.write(("--" + BOUNDARY + "\r\n").getBytes());
 			String filename = fileToUpload.getName();
-			if(gzip){
-				filename+=".gz";
+			if (gzip) {
+				filename += ".gz";
 			}
-			ous.write(("content-disposition: form-data; name=\""+formName+"\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
-	        ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
-	        InputStream fis = new FileInputStream(fileToUpload);
+			ous.write(("content-disposition: form-data; name=\"" + formName + "\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
+			ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
+			InputStream fis = new FileInputStream(fileToUpload);
 			BufferedInputStream bis = new BufferedInputStream(fis, 20 * 1024);
 			ous.flush();
-			if(gzip){
+			if (gzip) {
 				GZIPOutputStream gous = new GZIPOutputStream(ous, 1024);
 				Algorithms.streamCopy(bis, gous);
 				gous.flush();
@@ -112,8 +118,7 @@ public class NetworkUtils {
 			} else {
 				Algorithms.streamCopy(bis, ous);
 			}
-			
-	        ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
+			ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
 			ous.flush();
 			Algorithms.closeStream(bis);
 			Algorithms.closeStream(ous);
@@ -136,7 +141,6 @@ public class NetworkUtils {
 						responseBody.append("\n"); //$NON-NLS-1$
 					}
 					responseBody.append(s);
-					
 				}
 				is.close();
 			}
@@ -157,7 +161,6 @@ public class NetworkUtils {
 			proxy = null;
 		}
 	}
-	
 	public static Proxy getProxy() {
 		return proxy;
 	}
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmAndJDKHttpClient.java b/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmAndJDKHttpClient.java
new file mode 100644
index 0000000000..9b320119ee
--- /dev/null
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmAndJDKHttpClient.java
@@ -0,0 +1,259 @@
+package net.osmand.osm.oauth;
+
+import com.github.scribejava.core.exceptions.OAuthException;
+import com.github.scribejava.core.httpclient.HttpClient;
+import com.github.scribejava.core.httpclient.jdk.JDKHttpClientConfig;
+import com.github.scribejava.core.httpclient.jdk.JDKHttpFuture;
+import com.github.scribejava.core.httpclient.multipart.MultipartPayload;
+import com.github.scribejava.core.httpclient.multipart.MultipartUtils;
+import com.github.scribejava.core.model.*;
+import net.osmand.util.Algorithms;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+public class OsmAndJDKHttpClient implements HttpClient {
+	private static final String BOUNDARY = "CowMooCowMooCowCowCow";
+	private final JDKHttpClientConfig config;
+
+	public OsmAndJDKHttpClient() {
+		this(JDKHttpClientConfig.defaultConfig());
+	}
+
+	public OsmAndJDKHttpClient(JDKHttpClientConfig clientConfig) {
+		config = clientConfig;
+	}
+
+	@Override
+	public void close() {
+	}
+
+	@Override
+	public  Future executeAsync(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                                  byte[] bodyContents, OAuthAsyncRequestCallback callback, OAuthRequest.ResponseConverter converter) {
+
+		return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.BYTE_ARRAY, bodyContents, callback,
+				converter);
+	}
+
+	@Override
+	public  Future executeAsync(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                                  MultipartPayload bodyContents, OAuthAsyncRequestCallback callback,
+	                                  OAuthRequest.ResponseConverter converter) {
+
+		return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.MULTIPART, bodyContents, callback,
+				converter);
+	}
+
+	@Override
+	public  Future executeAsync(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                                  String bodyContents, OAuthAsyncRequestCallback callback, OAuthRequest.ResponseConverter converter) {
+
+		return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.STRING, bodyContents, callback,
+				converter);
+	}
+
+	@Override
+	public  Future executeAsync(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                                  File bodyContents, OAuthAsyncRequestCallback callback, OAuthRequest.ResponseConverter converter) {
+		return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.STREAM, bodyContents, callback,
+				converter);
+	}
+
+	private  Future doExecuteAsync(String userAgent, Map headers, Verb httpVerb,
+	                                     String completeUrl, BodyType bodyType, Object bodyContents, OAuthAsyncRequestCallback callback,
+	                                     OAuthRequest.ResponseConverter converter) {
+		try {
+			final Response response = doExecute(userAgent, headers, httpVerb, completeUrl, bodyType, bodyContents);
+			@SuppressWarnings("unchecked") final T t = converter == null ? (T) response : converter.convert(response);
+			if (callback != null) {
+				callback.onCompleted(t);
+			}
+			return new JDKHttpFuture<>(t);
+		} catch (IOException | RuntimeException e) {
+			if (callback != null) {
+				callback.onThrowable(e);
+			}
+			return new JDKHttpFuture<>(e);
+		}
+	}
+
+	@Override
+	public Response execute(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                        byte[] bodyContents) throws InterruptedException, ExecutionException, IOException {
+		return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.BYTE_ARRAY, bodyContents);
+	}
+
+	@Override
+	public Response execute(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                        MultipartPayload multipartPayloads) throws InterruptedException, ExecutionException, IOException {
+		return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.MULTIPART, multipartPayloads);
+	}
+
+	@Override
+	public Response execute(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                        String bodyContents) throws InterruptedException, ExecutionException, IOException {
+		return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.STRING, bodyContents);
+	}
+
+	@Override
+	public Response execute(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                        File bodyContents) throws InterruptedException, ExecutionException, IOException {
+		return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.STREAM, bodyContents);
+	}
+
+	private Response doExecute(String userAgent, Map headers, Verb httpVerb, String completeUrl,
+	                           BodyType bodyType, Object bodyContents) throws IOException {
+		final URL url = new URL(completeUrl);
+		final HttpURLConnection connection;
+		if (config.getProxy() == null) {
+			connection = (HttpURLConnection) url.openConnection();
+		} else {
+			connection = (HttpURLConnection) url.openConnection(config.getProxy());
+		}
+		connection.setInstanceFollowRedirects(config.isFollowRedirects());
+		connection.setRequestMethod(httpVerb.name());
+		if (config.getConnectTimeout() != null) {
+			connection.setConnectTimeout(config.getConnectTimeout());
+		}
+		if (config.getReadTimeout() != null) {
+			connection.setReadTimeout(config.getReadTimeout());
+		}
+		addHeaders(connection, headers, userAgent);
+		if (httpVerb.isPermitBody()) {
+			bodyType.setBody(connection, bodyContents, httpVerb.isRequiresBody());
+		}
+
+		try {
+			connection.connect();
+			final int responseCode = connection.getResponseCode();
+			return new Response(responseCode, connection.getResponseMessage(), parseHeaders(connection),
+					responseCode >= 200 && responseCode < 400 ? connection.getInputStream()
+							: connection.getErrorStream());
+		} catch (UnknownHostException e) {
+			throw new OAuthException("The IP address of a host could not be determined.", e);
+		}
+	}
+
+	private enum BodyType {
+		BYTE_ARRAY {
+			@Override
+			void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
+				addBody(connection, (byte[]) bodyContents, requiresBody);
+			}
+		},
+		STREAM {
+			@Override
+			void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
+				addBody(connection, (File) bodyContents, requiresBody);
+			}
+		},
+		MULTIPART {
+			@Override
+			void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
+				addBody(connection, (MultipartPayload) bodyContents, requiresBody);
+			}
+		},
+		STRING {
+			@Override
+			void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
+				addBody(connection, ((String) bodyContents).getBytes(), requiresBody);
+			}
+		};
+
+		abstract void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody)
+				throws IOException;
+	}
+
+	private static Map parseHeaders(HttpURLConnection conn) {
+		final Map headers = new HashMap<>();
+
+		for (Map.Entry> headerField : conn.getHeaderFields().entrySet()) {
+			final String key = headerField.getKey();
+			final String value = headerField.getValue().get(0);
+			if ("Content-Encoding".equalsIgnoreCase(key)) {
+				headers.put("Content-Encoding", value);
+			} else {
+				headers.put(key, value);
+			}
+		}
+		return headers;
+	}
+
+	private static void addHeaders(HttpURLConnection connection, Map headers, String userAgent) {
+		for (Map.Entry header : headers.entrySet()) {
+			connection.setRequestProperty(header.getKey(), header.getValue());
+		}
+
+		if (userAgent != null) {
+			connection.setRequestProperty(OAuthConstants.USER_AGENT_HEADER_NAME, userAgent);
+		}
+	}
+
+	private static void addBody(HttpURLConnection connection, File file, boolean requiresBody) throws IOException {
+		if (requiresBody) {
+			String filename = file.getName();
+			String formName = "file";
+			InputStream stream = new FileInputStream(file);
+			connection.setDoInput(true);
+			connection.setDoOutput(true);
+			connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); //$NON-NLS-1$ //$NON-NLS-2$
+			connection.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$
+			final OutputStream ous = connection.getOutputStream();
+			ous.write(("--" + BOUNDARY + "\r\n").getBytes());
+			ous.write(("content-disposition: form-data; name=\"" + formName + "\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
+			ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
+			BufferedInputStream bis = new BufferedInputStream(stream, 20 * 1024);
+			ous.flush();
+			Algorithms.streamCopy(bis, ous);
+			ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
+			ous.flush();
+			Algorithms.closeStream(bis);
+		}
+	}
+
+	private static void addBody(HttpURLConnection connection, byte[] content, boolean requiresBody) throws IOException {
+		final int contentLength = content.length;
+		if (requiresBody || contentLength > 0) {
+			connection.setDoOutput(true);
+			final OutputStream outputStream = prepareConnectionForBodyAndGetOutputStream(connection, contentLength);
+			if (contentLength > 0) {
+				outputStream.write(content);
+			}
+		}
+	}
+
+	private static void addBody(HttpURLConnection connection, MultipartPayload multipartPayload, boolean requiresBody)
+			throws IOException {
+
+		for (Map.Entry header : multipartPayload.getHeaders().entrySet()) {
+			connection.setRequestProperty(header.getKey(), header.getValue());
+		}
+
+		if (requiresBody) {
+			final ByteArrayOutputStream os = MultipartUtils.getPayload(multipartPayload);
+			final int contentLength = os.size();
+			connection.setDoOutput(true);
+			final OutputStream outputStream = prepareConnectionForBodyAndGetOutputStream(connection, contentLength);
+			if (contentLength > 0) {
+				os.writeTo(outputStream);
+			}
+		}
+	}
+
+	private static OutputStream prepareConnectionForBodyAndGetOutputStream(HttpURLConnection connection,
+	                                                                       int contentLength) throws IOException {
+		connection.setRequestProperty(CONTENT_LENGTH, String.valueOf(contentLength));
+		if (connection.getRequestProperty(CONTENT_TYPE) == null) {
+			connection.setRequestProperty(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
+		}
+		return connection.getOutputStream();
+	}
+}
\ No newline at end of file
diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmOAuthAuthorizationClient.java b/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmOAuthAuthorizationClient.java
new file mode 100644
index 0000000000..cf2f5a5acc
--- /dev/null
+++ b/OsmAnd-java/src/main/java/net/osmand/osm/oauth/OsmOAuthAuthorizationClient.java
@@ -0,0 +1,171 @@
+// License: GPL. For details, see LICENSE file.
+package net.osmand.osm.oauth;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.builder.api.DefaultApi10a;
+import com.github.scribejava.core.builder.api.OAuth1SignatureType;
+import com.github.scribejava.core.httpclient.jdk.JDKHttpClientConfig;
+import com.github.scribejava.core.model.*;
+import com.github.scribejava.core.oauth.OAuth10aService;
+import net.osmand.PlatformUtil;
+import org.apache.commons.logging.Log;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * An OAuth 1.0 authorization client.
+ *
+ * @since 2746
+ */
+public class OsmOAuthAuthorizationClient {
+    private OAuth1RequestToken requestToken;
+    private OAuth1AccessToken accessToken;
+    private final OAuth10aService service;
+    private final OsmAndJDKHttpClient httpClient;
+    public final static Log log = PlatformUtil.getLog(OsmOAuthAuthorizationClient.class);
+
+    public OsmOAuthAuthorizationClient(String key, String secret, DefaultApi10a api) {
+        httpClient = new OsmAndJDKHttpClient(JDKHttpClientConfig.defaultConfig());
+        service = new ServiceBuilder(key)
+                .apiSecret(secret)
+                .httpClient(httpClient)
+                .callback("osmand-oauth://example.com/oauth")
+                .build(api);
+    }
+
+    public static class OsmApi extends DefaultApi10a {
+        @Override
+        public OAuth1SignatureType getSignatureType() {
+            return OAuth1SignatureType.QUERY_STRING;
+        }
+
+        @Override
+        public String getRequestTokenEndpoint() {
+            return "https://www.openstreetmap.org/oauth/request_token";
+        }
+
+        @Override
+        public String getAccessTokenEndpoint() {
+            return "https://www.openstreetmap.org/oauth/access_token";
+        }
+
+        @Override
+        protected String getAuthorizationBaseUrl() {
+            return "https://www.openstreetmap.org/oauth/authorize";
+        }
+    }
+
+    public static class OsmDevApi extends DefaultApi10a {
+        @Override
+        public OAuth1SignatureType getSignatureType() {
+            return OAuth1SignatureType.QUERY_STRING;
+        }
+
+        @Override
+        public String getRequestTokenEndpoint() {
+            return "https://master.apis.dev.openstreetmap.org/oauth/request_token";
+        }
+
+        @Override
+        public String getAccessTokenEndpoint() {
+            return "https://master.apis.dev.openstreetmap.org/oauth/access_token";
+        }
+
+        @Override
+        protected String getAuthorizationBaseUrl() {
+            return "https://master.apis.dev.openstreetmap.org/oauth/authorize";
+        }
+    }
+
+    public OsmAndJDKHttpClient getHttpClient() {
+        return httpClient;
+    }
+
+    public OAuth10aService getService() {
+        return service;
+    }
+
+    public void setAccessToken(OAuth1AccessToken accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public OAuth1AccessToken getAccessToken() {
+        return accessToken;
+    }
+
+    public Response performRequestWithoutAuth(String url, String requestMethod, String requestBody)
+            throws InterruptedException, ExecutionException, IOException {
+        Verb verb = parseRequestMethod(requestMethod);
+        OAuthRequest req = new OAuthRequest(verb, url);
+        req.setPayload(requestBody);
+        return service.execute(req);
+    }
+
+    public void performGetRequest(String url, OAuthAsyncRequestCallback callback) {
+        if (accessToken == null) {
+            throw new IllegalStateException("Access token is null");
+        }
+        OAuthRequest req = new OAuthRequest(Verb.GET, url);
+        service.signRequest(accessToken, req);
+        service.execute(req, callback);
+    }
+
+    public Response performRequest(String url, String method, String body)
+            throws InterruptedException, ExecutionException, IOException {
+        service.getApi().getSignatureType();
+        if (accessToken == null) {
+            throw new IllegalStateException("Access token is null");
+        }
+        Verb verbMethod = parseRequestMethod(method);
+        OAuthRequest req = new OAuthRequest(verbMethod, url);
+        req.setPayload(body);
+        service.signRequest(accessToken, req);
+        req.addHeader("Content-Type", "application/xml");
+        return service.execute(req);
+    }
+
+    public OAuth1RequestToken startOAuth() {
+        try {
+            requestToken = service.getRequestToken();
+        } catch (IOException e) {
+            log.error(e);
+        } catch (InterruptedException e) {
+            log.error(e);
+        } catch (ExecutionException e) {
+            log.error(e);
+        }
+        return requestToken;
+    }
+
+    public OAuth1AccessToken authorize(String oauthVerifier) {
+        try {
+            setAccessToken(service.getAccessToken(requestToken, oauthVerifier));
+        } catch (IOException e) {
+            log.error(e);
+        } catch (InterruptedException e) {
+            log.error(e);
+        } catch (ExecutionException e) {
+            log.error(e);
+        }
+        return accessToken;
+    }
+
+    public boolean isValidToken() {
+        return !(accessToken == null);
+    }
+
+    private Verb parseRequestMethod(String method) {
+        Verb m = Verb.GET;
+        if (method.equals("POST")) {
+            m = Verb.POST;
+        }
+        if (method.equals("PUT")) {
+            m = Verb.PUT;
+        }
+        if (method.equals("DELETE")) {
+            m = Verb.DELETE;
+        }
+        return m;
+    }
+}
diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java
index e597386d47..0ae4314dd9 100644
--- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java
+++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java
@@ -41,7 +41,7 @@ public class RenderingRule {
 	public void init(Map attributes) {
 		ArrayList props = new ArrayList(attributes.size());
 		intProperties = new int[attributes.size()];
-		floatProperties = null;
+		floatProperties = new float[attributes.size()];
 		attributesRef = null;
 		int i = 0;
 		Iterator> it = attributes.entrySet().iterator();
@@ -58,14 +58,13 @@ public class RenderingRule {
 					attributesRef[i] = storage.getRenderingAttributeRule(vl.substring(1));
 				} else if (property.isString()) {
 					intProperties[i] = storage.getDictionaryValue(vl);
-				} else if (property.isFloat()) {
-					if (floatProperties == null) {
-						// lazy creates
-						floatProperties = new float[attributes.size()];
-					}
-					floatProperties[i] = property.parseFloatValue(vl);
-					intProperties[i] = property.parseIntValue(vl);
 				} else {
+					float floatVal = property.parseFloatValue(vl);
+//					if (floatProperties == null && floatVal != 0) {
+//						// lazy creates
+//						floatProperties = new float[attributes.size()];
+						floatProperties[i] = floatVal;
+//					}
 					intProperties[i] = property.parseIntValue(vl);
 				}
 				i++;
@@ -95,7 +94,7 @@ public class RenderingRule {
 	
 	public float getFloatPropertyValue(String property) {
 		int i = getPropertyIndex(property);
-		if(i >= 0 && floatProperties != null){
+		if (i >= 0) {
 			return floatProperties[i];
 		}
 		return 0;
diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java
index 228430b01f..322a734980 100644
--- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java
+++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleProperty.java
@@ -155,12 +155,7 @@ public class RenderingRuleProperty {
 			try {
 				int colon = value.indexOf(':');
 				if(colon != -1) {
-					int c  = 0;
-					if(colon > 0) {
-						c += (int) Float.parseFloat(value.substring(0, colon));
-					}
-					c += (int) Float.parseFloat(value.substring(colon + 1));
-					return c;
+					return (int) Float.parseFloat(value.substring(colon + 1));
 				}
 				return (int) Float.parseFloat(value);
 			} catch (NumberFormatException e) {
@@ -190,30 +185,35 @@ public class RenderingRuleProperty {
 			} catch (NumberFormatException e) {
 				log.error("Rendering parse " + value + " in " + attrName);
 			}
-			return -1;
+			return 0;
 		} else {
 			return -1;
 		}
 	}
 	
-	public float parseFloatValue(String value){
-		if(type == FLOAT_TYPE){
-			try {
+	public float parseFloatValue(String value) {
+		try {
+			if (type == FLOAT_TYPE) {
 				int colon = value.indexOf(':');
-				if(colon != -1) {
-					if(colon > 0) {
+				if (colon != -1) {
+					if (colon > 0) {
 						return Float.parseFloat(value.substring(0, colon));
-					} 
+					}
 					return 0;
 				}
 				return Float.parseFloat(value);
-			} catch (NumberFormatException e) {
-				log.error("Rendering parse " + value + " in " + attrName);
+
+			} else if (type == INT_TYPE) {
+				int colon = value.indexOf(':');
+				if (colon != -1 && colon > 0) {
+					return Float.parseFloat(value.substring(0, colon));
+				}
+				return 0;
 			}
-			return -1;
-		} else {
-			return -1;
+		} catch (NumberFormatException e) {
+			log.error("Rendering parse " + value + " in " + attrName);
 		}
+		return 0;
 	}
 	
 	
diff --git a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java
index 7a43f1f624..3eafb803dd 100644
--- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java
+++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRuleStorageProperties.java
@@ -244,8 +244,6 @@ public class RenderingRuleStorageProperties {
 		R_TEXT_HALO_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(TEXT_HALO_COLOR));
 		R_TEXT_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(TEXT_SIZE));
 		R_TEXT_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(TEXT_ORDER));
-		R_ICON_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(ICON_ORDER));
-		R_ICON_VISIBLE_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(ICON_VISIBLE_SIZE));
 		R_TEXT_MIN_DISTANCE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(TEXT_MIN_DISTANCE));
 		R_TEXT_SHIELD = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty(TEXT_SHIELD));
 		
@@ -265,7 +263,9 @@ public class RenderingRuleStorageProperties {
 		R_ICON_3 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_3"));
 		R_ICON_4 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_4"));
 		R_ICON_5 = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty("icon_5"));
+		R_ICON_ORDER = registerRuleInternal(RenderingRuleProperty.createOutputIntProperty(ICON_ORDER));
 		R_SHIELD = registerRuleInternal(RenderingRuleProperty.createOutputStringProperty(SHIELD));
+		R_ICON_VISIBLE_SIZE = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(ICON_VISIBLE_SIZE));
 
 		// polygon/way
 		R_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(COLOR));
diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteExporter.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteExporter.java
index 7ae47d4098..6add39fdcc 100644
--- a/OsmAnd-java/src/main/java/net/osmand/router/RouteExporter.java
+++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteExporter.java
@@ -20,10 +20,10 @@ public class RouteExporter {
 
 	public static final String OSMAND_ROUTER_V2 = "OsmAndRouterV2";
 
-	private String name;
-	private List route;
-	private List locations;
-	private List points;
+	private final String name;
+	private final List route;
+	private final List locations;
+	private final List points;
 
 	public RouteExporter(String name, List route, List locations, List points) {
 		this.name = name;
@@ -33,6 +33,34 @@ public class RouteExporter {
 	}
 
 	public GPXFile exportRoute() {
+		GPXFile gpx = new GPXFile(OSMAND_ROUTER_V2);
+		Track track = new Track();
+		track.name = name;
+		gpx.tracks.add(track);
+		track.segments.add(generateRouteSegment());
+		if (points != null) {
+			for (WptPt pt : points) {
+				gpx.addPoint(pt);
+			}
+		}
+		return gpx;
+	}
+
+	public static GPXFile exportRoute(String name, List trkSegments, List points) {
+		GPXFile gpx = new GPXFile(OSMAND_ROUTER_V2);
+		Track track = new Track();
+		track.name = name;
+		gpx.tracks.add(track);
+		track.segments.addAll(trkSegments);
+		if (points != null) {
+			for (WptPt pt : points) {
+				gpx.addPoint(pt);
+			}
+		}
+		return gpx;
+	}
+
+	public TrkSegment generateRouteSegment() {
 		RouteDataResources resources = new RouteDataResources(locations);
 		List routeItems = new ArrayList<>();
 		if (!Algorithms.isEmpty(route)) {
@@ -57,15 +85,9 @@ public class RouteExporter {
 			typeList.add(typeBundle);
 		}
 
-		GPXFile gpx = new GPXFile(OSMAND_ROUTER_V2);
-		Track track = new Track();
-		track.name = name;
-		gpx.tracks.add(track);
 		TrkSegment trkSegment = new TrkSegment();
-		track.segments.add(trkSegment);
-
 		if (locations == null || locations.isEmpty()) {
-			return gpx;
+			return trkSegment;
 		}
 		for (int i = 0; i < locations.size(); i++) {
 			Location loc = locations.get(i);
@@ -83,23 +105,17 @@ public class RouteExporter {
 			}
 			trkSegment.points.add(pt);
 		}
-		if (points != null) {
-			for (WptPt pt : points) {
-				gpx.addPoint(pt);
-			}
-		}
 
 		List routeSegments = new ArrayList<>();
 		for (StringBundle item : routeItems) {
 			routeSegments.add(RouteSegment.fromStringBundle(item));
 		}
-		gpx.routeSegments = routeSegments;
+		trkSegment.routeSegments = routeSegments;
 		List routeTypes = new ArrayList<>();
 		for (StringBundle item : typeList) {
 			routeTypes.add(RouteType.fromStringBundle(item));
 		}
-		gpx.routeTypes = routeTypes;
-
-		return gpx;
+		trkSegment.routeTypes = routeTypes;
+		return trkSegment;
 	}
 }
diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteImporter.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteImporter.java
index e2be849dcb..418ca2db6c 100644
--- a/OsmAnd-java/src/main/java/net/osmand/router/RouteImporter.java
+++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteImporter.java
@@ -4,6 +4,7 @@ import net.osmand.GPXUtilities;
 import net.osmand.GPXUtilities.GPXFile;
 import net.osmand.GPXUtilities.RouteSegment;
 import net.osmand.GPXUtilities.RouteType;
+import net.osmand.GPXUtilities.TrkSegment;
 import net.osmand.GPXUtilities.WptPt;
 import net.osmand.Location;
 import net.osmand.PlatformUtil;
@@ -28,10 +29,9 @@ public class RouteImporter {
 
 	private File file;
 	private GPXFile gpxFile;
+	private TrkSegment segment;
 
-	private List route = new ArrayList<>();
-	private RouteRegion region = new RouteRegion();
-	private RouteDataResources resources = new RouteDataResources();
+	private final List route = new ArrayList<>();
 
 	public RouteImporter(File file) {
 		this.file = file;
@@ -41,8 +41,12 @@ public class RouteImporter {
 		this.gpxFile = gpxFile;
 	}
 
+	public RouteImporter(TrkSegment segment) {
+		this.segment = segment;
+	}
+
 	public List importRoute() {
-		if (gpxFile != null) {
+		if (gpxFile != null || segment != null) {
 			parseRoute();
 		} else if (file != null) {
 			FileInputStream fis = null;
@@ -69,19 +73,34 @@ public class RouteImporter {
 	}
 
 	private void parseRoute() {
-		collectLocations();
-		collectSegments();
-		collectTypes();
-		for (RouteSegmentResult segment : route) {
-			segment.fillNames(resources);
+		if (segment != null) {
+			parseRoute(segment);
+		} else if (gpxFile != null) {
+			List segments = gpxFile.getNonEmptyTrkSegments(true);
+			for (TrkSegment s : segments) {
+				parseRoute(s);
+			}
 		}
 	}
 
-	private void collectLocations() {
+	private void parseRoute(TrkSegment segment) {
+		RouteRegion region = new RouteRegion();
+		RouteDataResources resources = new RouteDataResources();
+
+		collectLocations(resources, segment);
+		List route = collectRouteSegments(region, resources, segment);
+		collectRouteTypes(region, segment);
+		for (RouteSegmentResult routeSegment : route) {
+			routeSegment.fillNames(resources);
+		}
+		this.route.addAll(route);
+	}
+
+	private void collectLocations(RouteDataResources resources, TrkSegment segment) {
 		List locations = resources.getLocations();
 		double lastElevation = HEIGHT_UNDEFINED;
-		if (gpxFile.tracks.size() > 0 && gpxFile.tracks.get(0).segments.size() > 0 && gpxFile.tracks.get(0).segments.get(0).points.size() > 0) {
-			for (WptPt point : gpxFile.tracks.get(0).segments.get(0).points) {
+		if (segment.hasRoute()) {
+			for (WptPt point : segment.points) {
 				Location loc = new Location("", point.getLatitude(), point.getLongitude());
 				if (!Double.isNaN(point.ele)) {
 					loc.setAltitude(point.ele);
@@ -94,18 +113,20 @@ public class RouteImporter {
 		}
 	}
 
-	private void collectSegments() {
-		for (RouteSegment segment : gpxFile.routeSegments) {
+	private List collectRouteSegments(RouteRegion region, RouteDataResources resources, TrkSegment segment) {
+		List route = new ArrayList<>();
+		for (RouteSegment routeSegment : segment.routeSegments) {
 			RouteDataObject object = new RouteDataObject(region);
 			RouteSegmentResult segmentResult = new RouteSegmentResult(object);
-			segmentResult.readFromBundle(new RouteDataBundle(resources, segment.toStringBundle()));
+			segmentResult.readFromBundle(new RouteDataBundle(resources, routeSegment.toStringBundle()));
 			route.add(segmentResult);
 		}
+		return route;
 	}
 
-	private void collectTypes() {
+	private void collectRouteTypes(RouteRegion region, TrkSegment segment) {
 		int i = 0;
-		for (RouteType routeType : gpxFile.routeTypes) {
+		for (RouteType routeType : segment.routeTypes) {
 			StringBundle bundle = routeType.toStringBundle();
 			String t = bundle.getString("t", null);
 			String v = bundle.getString("v", null);
diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java
index 5aefb2ac44..17eb567813 100644
--- a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java
+++ b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java
@@ -256,7 +256,7 @@ public class RoutePlannerFrontEnd {
 						if (routeFound) {
 							// route is found - cut the end of the route and move to next iteration
 //							start.stepBackRoute = new ArrayList();
-//							boolean stepBack = true; 
+//							boolean stepBack = true;
 							boolean stepBack = stepBackAndFindPrevPointInRoute(gctx, gpxPoints, start, next);
 							if (!stepBack) {
 								// not supported case (workaround increase MAXIMUM_STEP_APPROXIMATION)
@@ -546,8 +546,10 @@ public class RoutePlannerFrontEnd {
 		if (start != null && start.pnt == null) {
 			gctx.routePointsSearched++;
 			RouteSegmentPoint rsp = findRouteSegment(start.loc.getLatitude(), start.loc.getLongitude(), gctx.ctx, null, false);
-			if (MapUtils.getDistance(rsp.getPreciseLatLon(), start.loc) < distThreshold) {
-				start.pnt = rsp;
+			if (rsp != null) {
+				if (MapUtils.getDistance(rsp.getPreciseLatLon(), start.loc) < distThreshold) {
+					start.pnt = rsp;
+				}
 			}
  		} 
 		if (start != null && start.pnt != null) {
@@ -734,7 +736,7 @@ public class RoutePlannerFrontEnd {
 			res = searchRouteImpl(ctx, points, routeDirection);
 		}
 		if (ctx.calculationProgress != null) {
-			ctx.calculationProgress.timeToCalculate += (System.nanoTime() - timeToCalculate);
+			ctx.calculationProgress.timeToCalculate = (System.nanoTime() - timeToCalculate);
 		}
 		BinaryRoutePlanner.printDebugMemoryInformation(ctx);
 		if (res != null) {
diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteSegmentResult.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteSegmentResult.java
index 2aad93fd77..6f8de67353 100644
--- a/OsmAnd-java/src/main/java/net/osmand/router/RouteSegmentResult.java
+++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteSegmentResult.java
@@ -255,7 +255,8 @@ public class RouteSegmentResult implements StringExternalizable
 	@Override
 	public void writeToBundle(RouteDataBundle bundle) {
 		Map rules = bundle.getResources().getRules();
-		bundle.putInt("length", (Math.abs(endPointIndex - startPointIndex) + 1) * (endPointIndex >= startPointIndex ? 1 : -1));
+		boolean reversed = endPointIndex < startPointIndex;
+		bundle.putInt("length", Math.abs(endPointIndex - startPointIndex) + 1);
 		bundle.putFloat("segmentTime", segmentTime, 2);
 		bundle.putFloat("speed", speed, 2);
 		if (turnType != null) {
@@ -271,24 +272,29 @@ public class RouteSegmentResult implements StringExternalizable
 				bundle.putString("turnLanes", TurnType.lanesToString(turnLanes));
 			}
 		}
-		bundle.putLong("id", object.id);
+		bundle.putLong("id", object.id >> 6); // OsmAnd ID to OSM ID
 		bundle.putArray("types", convertTypes(object.types, rules));
 
 		int start = Math.min(startPointIndex, endPointIndex);
 		int end = Math.max(startPointIndex, endPointIndex) + 1;
 		if (object.pointTypes != null && start < object.pointTypes.length) {
 			int[][] types = Arrays.copyOfRange(object.pointTypes, start, Math.min(end, object.pointTypes.length));
+			if (reversed) {
+				Algorithms.reverseArray(types);
+			}
 			bundle.putArray("pointTypes", convertTypes(types, rules));
 		}
 		if (object.nameIds != null) {
 			bundle.putArray("names", convertNameIds(object.nameIds, rules));
 		}
-		if (object.pointNameTypes != null && start < object.pointNameTypes.length) {
+		if (object.pointNameTypes != null && start < object.pointNameTypes.length && object.pointNames != null) {
 			int[][] types = Arrays.copyOfRange(object.pointNameTypes, start, Math.min(end, object.pointNameTypes.length));
-			if (object.pointNames != null) {
-				String[][] names = Arrays.copyOfRange(object.pointNames, start, Math.min(end, object.pointNames.length));
-				bundle.putArray("pointNames", convertPointNames(types, names, rules));
+			String[][] names = Arrays.copyOfRange(object.pointNames, start, Math.min(end, object.pointNames.length));
+			if (reversed) {
+				Algorithms.reverseArray(types);
+				Algorithms.reverseArray(names);
 			}
+			bundle.putArray("pointNames", convertPointNames(types, names, rules));
 		}
 	}
 
@@ -327,22 +333,21 @@ public class RouteSegmentResult implements StringExternalizable
 		Location prevLocation = null;
 		for (int i = 0; i < length; i++) {
 			Location location = resources.getLocation(index);
-			if (location == null) {
-				break;
-			}
-			double dist = 0;
-			if (prevLocation != null) {
-				dist = MapUtils.getDistance(prevLocation.getLatitude(), prevLocation.getLongitude(), location.getLatitude(), location.getLongitude());
-				distance += dist;
-			}
-			prevLocation = location;
-			object.pointsX[i] = MapUtils.get31TileNumberX(location.getLongitude());
-			object.pointsY[i] = MapUtils.get31TileNumberY(location.getLatitude());
-			if (location.hasAltitude() && object.heightDistanceArray.length > 0) {
-				object.heightDistanceArray[i * 2] = (float) dist;
-				object.heightDistanceArray[i * 2 + 1] = (float) location.getAltitude();
-			} else {
-				object.heightDistanceArray = new float[0];
+			if (location != null) {
+				double dist = 0;
+				if (prevLocation != null) {
+					dist = MapUtils.getDistance(prevLocation.getLatitude(), prevLocation.getLongitude(), location.getLatitude(), location.getLongitude());
+					distance += dist;
+				}
+				prevLocation = location;
+				object.pointsX[i] = MapUtils.get31TileNumberX(location.getLongitude());
+				object.pointsY[i] = MapUtils.get31TileNumberY(location.getLatitude());
+				if (location.hasAltitude() && object.heightDistanceArray.length > 0) {
+					object.heightDistanceArray[i * 2] = (float) dist;
+					object.heightDistanceArray[i * 2 + 1] = (float) location.getAltitude();
+				} else {
+					object.heightDistanceArray = new float[0];
+				}
 			}
 			if (plus) {
 				index++;
diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java b/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java
index 922e225855..3bfbaec0da 100644
--- a/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java
+++ b/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java
@@ -289,7 +289,8 @@ public class RoutingContext {
 								if(excludeNotAllowed != null && !excludeNotAllowed.contains(ro.getId())) {
 									ts.add(ro);
 								}
-							} else if(excludeNotAllowed != null && ro.getId() > 0){
+							}
+							if(excludeNotAllowed != null && ro.getId() > 0){
 								excludeNotAllowed.add(ro.getId());
 								if(ts.excludedIds == null ){
 									ts.excludedIds = new TLongHashSet();
diff --git a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java
index becacd2775..e63870b1a7 100644
--- a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java
+++ b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java
@@ -741,7 +741,7 @@ public class SearchUICore {
 					}
 				}
 				if (Algorithms.isEmpty(object.alternateName) && object.object instanceof Amenity) {
-					for (String value : ((Amenity) object.object).getAdditionalInfo().values()) {
+					for (String value : ((Amenity) object.object).getAdditionalInfoValues(true)) {
 						if (phrase.getFirstUnknownNameStringMatcher().matches(value)) {
 							object.alternateName = value;
 							break;
diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java
index a4916d4187..c221d2ca77 100644
--- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java
+++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java
@@ -33,6 +33,7 @@ import net.osmand.util.LocationParser.ParsedOpenLocationCode;
 import net.osmand.util.MapUtils;
 
 import java.io.IOException;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -601,7 +602,7 @@ public class SearchCoreFactory {
 							sr.localeName = object.getName(phrase.getSettings().getLang(),
 									phrase.getSettings().isTransliterate());
 							if (!nm.matches(sr.localeName) && !nm.matches(sr.otherNames)
-									&& !nm.matches(object.getAdditionalInfo().values())) {
+									&& !nm.matches(object.getAdditionalInfoValues(false))) {
 								return false;
 							}
 							sr.object = object;
@@ -922,6 +923,7 @@ public class SearchCoreFactory {
 
 	public static class SearchAmenityByTypeAPI extends SearchBaseAPI {
 		private static final int BBOX_RADIUS = 10000;
+		private static final int BBOX_RADIUS_NEAREST = 1000;
 		private SearchAmenityTypesAPI searchAmenityTypesAPI;
 		private MapPoiTypes types;
 		private AbstractPoiType unselectedPoiType;
@@ -1006,7 +1008,14 @@ public class SearchCoreFactory {
 			}
 			this.nameFilter = nameFilter;
 			if (poiTypeFilter != null) {
-				QuadRect bbox = phrase.getRadiusBBoxToSearch(BBOX_RADIUS);
+				int radius = BBOX_RADIUS;
+				if (phrase.getRadiusLevel() == 1 && poiTypeFilter instanceof CustomSearchPoiFilter) {
+					String name = ((CustomSearchPoiFilter) poiTypeFilter).getFilterId();
+					if ("std_null".equals(name)) {
+						radius = BBOX_RADIUS_NEAREST;
+					}
+				}
+				QuadRect bbox = phrase.getRadiusBBoxToSearch(radius);
 				List offlineIndexes = phrase.getOfflineIndexes();
 				Set searchedPois = new TreeSet<>();
 				for (BinaryMapIndexReader r : offlineIndexes) {
@@ -1050,7 +1059,7 @@ public class SearchCoreFactory {
 					if (!poiAdditionals.isEmpty()) {
 						boolean found = false;
 						for (String add : poiAdditionals) {
-							if(object.getAdditionalInfo().containsKey(add)) {
+							if (object.getAdditionalInfoKeys().contains(add)) {
 								found = true;
 								break;
 							}
@@ -1407,6 +1416,7 @@ public class SearchCoreFactory {
 		private LatLon olcPhraseLocation;
 		private ParsedOpenLocationCode cachedParsedCode;
 		private final List citySubTypes = Arrays.asList("city", "town", "village");
+		private final DecimalFormat latLonFormatter = new DecimalFormat("#.0####");
 
 		public SearchLocationAndUrlAPI() {
 			super(ObjectType.LOCATION, ObjectType.PARTIAL_LOCATION);
@@ -1498,7 +1508,7 @@ public class SearchCoreFactory {
 						sp.priority = SEARCH_LOCATION_PRIORITY;
 
 						sp.object = sp.location = ll;
-						sp.localeName = ((float) sp.location.getLatitude()) + ",  ";
+						sp.localeName = formatLatLon(sp.location.getLatitude()) + ",  ";
 						sp.objectType = ObjectType.PARTIAL_LOCATION;
 						resultMatcher.publish(sp);
 					}
@@ -1510,7 +1520,7 @@ public class SearchCoreFactory {
 			SearchResult sp = new SearchResult(phrase);
 			sp.priority = SEARCH_LOCATION_PRIORITY;
 			sp.object = sp.location = l;
-			sp.localeName = ((float) sp.location.getLatitude()) + ", " + ((float) sp.location.getLongitude());
+			sp.localeName = formatLatLon(sp.location.getLatitude()) + ", " + formatLatLon(sp.location.getLongitude());
 			sp.objectType = ObjectType.LOCATION;
 			sp.wordsSpan = lw;
 			resultMatcher.publish(sp);
@@ -1525,7 +1535,7 @@ public class SearchCoreFactory {
 				sp.object = pnt;
 				sp.wordsSpan = text;
 				sp.location = new LatLon(pnt.getLatitude(), pnt.getLongitude());
-				sp.localeName = ((float)pnt.getLatitude()) +", " + ((float) pnt.getLongitude());
+				sp.localeName = formatLatLon(pnt.getLatitude()) +", " + formatLatLon(pnt.getLongitude());
 				if (pnt.getZoom() > 0) {
 					sp.preferredZoom = pnt.getZoom();
 				}
@@ -1555,6 +1565,10 @@ public class SearchCoreFactory {
 			}
 			return cachedParsedCode == null ? SEARCH_LOCATION_PRIORITY : SEARCH_MAX_PRIORITY;
 		}
+
+		private String formatLatLon(double latLon) {
+			return latLonFormatter.format(latLon);
+		}
 	}
 
 	private static String stripBraces(String localeName) {
diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java
index f1b5b45b05..2a3fc59521 100644
--- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java
+++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java
@@ -229,12 +229,14 @@ public class SearchPhrase {
 	}
 	
 	public int countWords(String w) {
-		String[] ws = w.split(ALLDELIMITERS);
 		int cnt = 0;
-		for (int i = 0; i < ws.length; i++) {
-			String wd = ws[i].trim();
-			if (wd.length() > 0) {
-				cnt++;
+		if (!Algorithms.isEmpty(w)) {
+			String[] ws = w.split(ALLDELIMITERS);
+			for (int i = 0; i < ws.length; i++) {
+				String wd = ws[i].trim();
+				if (wd.length() > 0) {
+					cnt++;
+				}
 			}
 		}
 		return cnt;
diff --git a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java
index 21e7fbd58c..2c0c056ab3 100644
--- a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java
+++ b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java
@@ -49,6 +49,11 @@ public class Algorithms {
 	private static char[] CHARS_TO_NORMALIZE_KEY = new char['’'];
 	private static char[] CHARS_TO_NORMALIZE_VALUE = new char['\''];
 
+	public static final int ZIP_FILE_SIGNATURE = 0x504b0304;
+	public static final int XML_FILE_SIGNATURE = 0x3c3f786d;
+	public static final int OBF_FILE_SIGNATURE = 0x08029001;
+	public static final int SQLITE_FILE_SIGNATURE = 0x53514C69;
+
 	public static String normalizeSearchText(String s) {
 		boolean norm = false;
 		for (int i = 0; i < s.length() && !norm; i++) {
@@ -119,9 +124,11 @@ public class Algorithms {
 	}
 
 	public static String getFileNameWithoutExtension(String name) {
-		int i = name.indexOf('.');
-		if (i >= 0) {
-			name = name.substring(0, i);
+		if (name != null) {
+			int index = name.lastIndexOf('.');
+			if (index != -1) {
+				return name.substring(0, index);
+			}
 		}
 		return name;
 	}
@@ -293,7 +300,7 @@ public class Algorithms {
 		FileInputStream in = new FileInputStream(file);
 		int test = readInt(in);
 		in.close();
-		return test == 0x504b0304;
+		return test == ZIP_FILE_SIGNATURE;
 	}
 
 	/**
@@ -322,7 +329,7 @@ public class Algorithms {
 		return false;
 	}
 
-	private static int readInt(InputStream in) throws IOException {
+	public static int readInt(InputStream in) throws IOException {
 		int ch1 = in.read();
 		int ch2 = in.read();
 		int ch3 = in.read();
@@ -879,6 +886,14 @@ public class Algorithms {
 		return map;
 	}
 
+	public static  void reverseArray(T[] array) {
+		for (int i = 0; i < array.length / 2; i++) {
+			T temp = array[i];
+			array[i] = array[array.length - i - 1];
+			array[array.length - i - 1] = temp;
+		}
+	}
+
 	public static boolean containsInArrayL(long[] array, long value) {
 		return Arrays.binarySearch(array, value) >= 0;
 	}
@@ -942,4 +957,20 @@ public class Algorithms {
 		}
 		return res;
 	}
+
+	public static boolean isValidMessageFormat(CharSequence sequence) {
+		if (!isEmpty(sequence)) {
+			int counter = 0;
+			for (int i = 0; i < sequence.length(); i++) {
+				char ch = sequence.charAt(i);
+				if (ch == '{') {
+					counter++;
+				} else if (ch == '}') {
+					counter--;
+				}
+			}
+			return counter == 0;
+		}
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/OsmAnd-java/src/main/java/net/osmand/util/LocationParser.java b/OsmAnd-java/src/main/java/net/osmand/util/LocationParser.java
index 100c4f014d..efa4cdaf42 100644
--- a/OsmAnd-java/src/main/java/net/osmand/util/LocationParser.java
+++ b/OsmAnd-java/src/main/java/net/osmand/util/LocationParser.java
@@ -3,6 +3,7 @@ package net.osmand.util;
 import com.google.openlocationcode.OpenLocationCode;
 import com.google.openlocationcode.OpenLocationCode.CodeArea;
 import com.jwetherell.openmap.common.LatLonPoint;
+import com.jwetherell.openmap.common.MGRSPoint;
 import com.jwetherell.openmap.common.UTMPoint;
 
 import net.osmand.data.LatLon;
@@ -111,7 +112,7 @@ public class LocationParser {
 			return null;
 		}
 		// detect UTM
-		if (all.size() == 4 && d.size() == 3 && all.get(1) instanceof String) {
+		if (all.size() == 4 && d.size() == 3 && all.get(1) instanceof String && ((String) all.get(1)).length() == 1) {
 			char ch = all.get(1).toString().charAt(0);
 			if (Character.isLetter(ch)) {
 				UTMPoint upoint = new UTMPoint(d.get(2), d.get(1), d.get(0).intValue(), ch);
@@ -120,7 +121,7 @@ public class LocationParser {
 			}
 		}
 
-		if (all.size() == 3 && d.size() == 2 && all.get(1) instanceof String) {
+		if (all.size() == 3 && d.size() == 2 && all.get(1) instanceof String && ((String) all.get(1)).length() == 1) {
 			char ch = all.get(1).toString().charAt(0);
 			String combined = strings.get(2);
 			if (Character.isLetter(ch)) {
@@ -135,6 +136,17 @@ public class LocationParser {
 				}
 			}
 		}
+
+		//detect MGRS
+		if (all.size() >= 3 && (d.size() == 2 || d.size() == 3) && all.get(1) instanceof String) {
+			try {
+				MGRSPoint mgrsPoint = new MGRSPoint(locPhrase);
+				LatLonPoint ll = mgrsPoint.toLatLonPoint();
+				return validateAndCreateLatLon(ll.getLatitude(), ll.getLongitude());
+			} catch (NumberFormatException e) {
+				//do nothing
+			}
+		}
 		// try to find split lat/lon position
 		int jointNumbers = 0;
 		int lastJoin = 0;
diff --git a/OsmAnd-java/src/test/java/net/osmand/router/RouteTestingTest.java b/OsmAnd-java/src/test/java/net/osmand/router/RouteTestingTest.java
index b4f343da62..06e6ef8fda 100644
--- a/OsmAnd-java/src/test/java/net/osmand/router/RouteTestingTest.java
+++ b/OsmAnd-java/src/test/java/net/osmand/router/RouteTestingTest.java
@@ -15,6 +15,7 @@ import java.util.TreeSet;
 
 import net.osmand.binary.BinaryMapIndexReader;
 
+import net.osmand.data.LatLon;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -29,42 +30,52 @@ public class RouteTestingTest {
 	private TestEntry te;
 
 
-    public RouteTestingTest(String name, TestEntry te) {
-        this.te = te;
-    }
+	public RouteTestingTest(String name, TestEntry te) {
+		this.te = te;
+	}
 
-    @BeforeClass
-    public static void setUp() throws Exception {
-        RouteResultPreparation.PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = true;
-    }
+	@BeforeClass
+	public static void setUp() throws Exception {
+		RouteResultPreparation.PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = true;
+	}
 
-    @Parameterized.Parameters(name = "{index}: {0}")
-    public static Iterable data() throws IOException {
-        String fileName = "/test_routing.json";
-        Reader reader = new InputStreamReader(RouteTestingTest.class.getResourceAsStream(fileName));
-        Gson gson = new GsonBuilder().setPrettyPrinting().create();
-        TestEntry[] testEntries = gson.fromJson(reader, TestEntry[].class);
-        ArrayList arrayList = new ArrayList<>();
-        for(TestEntry te : testEntries) {
-        	if(te.isIgnore()) {
-        		continue;
-        	}
-        	arrayList.add(new Object[] {te.getTestName(), te});
-        }
-        reader.close();
-        return arrayList;
+	@Parameterized.Parameters(name = "{index}: {0}")
+	public static Iterable data() throws IOException {
+		String fileName = "/test_routing.json";
+		Reader reader = new InputStreamReader(RouteTestingTest.class.getResourceAsStream(fileName));
+		Gson gson = new GsonBuilder().setPrettyPrinting().create();
+		TestEntry[] testEntries = gson.fromJson(reader, TestEntry[].class);
+		ArrayList arrayList = new ArrayList<>();
+		for (TestEntry te : testEntries) {
+			if (te.isIgnore()) {
+				continue;
+			}
+			arrayList.add(new Object[]{te.getTestName(), te});
+		}
+		reader.close();
+		return arrayList;
 
-    }
+	}
 
-    @Test
+	@Test
 	public void testRouting() throws Exception {
 		String fl = "src/test/resources/Routing_test.obf";
 		RandomAccessFile raf = new RandomAccessFile(fl, "r");
 		RoutePlannerFrontEnd fe = new RoutePlannerFrontEnd();
 
-		BinaryMapIndexReader[] binaryMapIndexReaders = { new BinaryMapIndexReader(raf, new File(fl)) };
+		BinaryMapIndexReader[] binaryMapIndexReaders;// = { new BinaryMapIndexReader(raf, new File(fl)) };
 		RoutingConfiguration.Builder builder = RoutingConfiguration.getDefault();
 		Map params = te.getParams();
+		if (params.containsKey("map")) {
+			String fl1 = "src/test/resources/" + params.get("map");
+			RandomAccessFile raf1 = new RandomAccessFile(fl1, "r");
+			binaryMapIndexReaders = new BinaryMapIndexReader[]{
+					new BinaryMapIndexReader(raf1, new File(fl1)),
+					new BinaryMapIndexReader(raf, new File(fl))
+			};
+		} else {
+			binaryMapIndexReaders = new BinaryMapIndexReader[]{new BinaryMapIndexReader(raf, new File(fl))};
+		}
 		RoutingConfiguration config = builder.build(params.containsKey("vehicle") ? params.get("vehicle") : "car",
 				RoutingConfiguration.DEFAULT_MEMORY_LIMIT * 3, params);
 		RoutingContext ctx = fe.buildRoutingContext(config, null, binaryMapIndexReaders,
@@ -102,6 +113,4 @@ public class RouteTestingTest {
 
 	}
 
-
-
 }
diff --git a/OsmAnd-telegram/AndroidManifest.xml b/OsmAnd-telegram/AndroidManifest.xml
index 7b2a96c236..73e2e856ca 100644
--- a/OsmAnd-telegram/AndroidManifest.xml
+++ b/OsmAnd-telegram/AndroidManifest.xml
@@ -20,7 +20,7 @@
         android:screenOrientation="unspecified"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
-
+        
         
+
+
+    
+
+        
+
+            
+
+        
+
+    
+
+    
+
+
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml
index 928ad6f319..1c5738a313 100644
--- a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml
+++ b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml
@@ -447,6 +447,50 @@
 
 			
 
+			
+
+			
+
+				
+
+					
+
+					
+
+				
+
+			
+
 			
 
 		
diff --git a/OsmAnd-telegram/res/layout/item_description_long.xml b/OsmAnd-telegram/res/layout/item_description_long.xml
new file mode 100644
index 0000000000..6face5220f
--- /dev/null
+++ b/OsmAnd-telegram/res/layout/item_description_long.xml
@@ -0,0 +1,18 @@
+
+
diff --git a/OsmAnd-telegram/res/values-ar-rSA/strings.xml b/OsmAnd-telegram/res/values-ar-rSA/strings.xml
index da6f0efd1b..f799745114 100644
--- a/OsmAnd-telegram/res/values-ar-rSA/strings.xml
+++ b/OsmAnd-telegram/res/values-ar-rSA/strings.xml
@@ -267,4 +267,8 @@
     مشاركة: %1$s
     مفعل
     %1$s منذ
+    تصدير
+    لوجكات العازلة
+    تحقق من السجلات التفصيلية للتطبيق وشاركها
+    إرسال تقرير
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-ar/strings.xml b/OsmAnd-telegram/res/values-ar/strings.xml
index 1fb4149e0b..8c0f48d893 100644
--- a/OsmAnd-telegram/res/values-ar/strings.xml
+++ b/OsmAnd-telegram/res/values-ar/strings.xml
@@ -38,7 +38,7 @@
     كلمة المرور
     استمرار
     إلغاء
-    الإعدادات
+    إعدادات
     المسافة
     ياردة
     قدم
@@ -267,4 +267,8 @@
     تتبع حالة أوسماند
     العودة إلى OsmAnd
     %1$s منذ
+    إرسال التقرير
+    تصدير
+    سجل الاستخدام
+    التحقق من السجلات التفصيلية للتطبيق ومشاركتها
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-be/strings.xml b/OsmAnd-telegram/res/values-be/strings.xml
index e1c5661a33..18e71dcd2e 100644
--- a/OsmAnd-telegram/res/values-be/strings.xml
+++ b/OsmAnd-telegram/res/values-be/strings.xml
@@ -267,4 +267,8 @@
     Апошні адказ: %1$s таму
     %1$s таму
     ERR
+    Даслаць справаздачу
+    Экспартаваць
+    Буфер logcat
+    Праверце і падзяліцеся падрабязнымі журналамі праграмы
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-bg/strings.xml b/OsmAnd-telegram/res/values-bg/strings.xml
new file mode 100644
index 0000000000..f27c2e8687
--- /dev/null
+++ b/OsmAnd-telegram/res/values-bg/strings.xml
@@ -0,0 +1,8 @@
+
+
+    Изпращане на доклад
+    Последен отговор: %1$s
+    Последна актуализация от Telegram: %1$s
+    ГРЕШКА
+    Експорт
+
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-ca/strings.xml b/OsmAnd-telegram/res/values-ca/strings.xml
index 9264c35526..26e15f88a1 100644
--- a/OsmAnd-telegram/res/values-ca/strings.xml
+++ b/OsmAnd-telegram/res/values-ca/strings.xml
@@ -131,7 +131,7 @@
     Envia la meva ubicació
     Ubicació
     Temps de compartició
-    "Expiració: "
+    Finalitza
     Obre l\'OsmAnd
     En directe
     Bot
@@ -267,4 +267,8 @@
     Darrera resposta: fa %1$s
     fa %1$s
     ERR
+    Exporta
+    Memòria intermèdia del Logcat
+    Valida i comparteix enregistraments detallats de l\'aplicació
+    Envia un informe
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-cs/strings.xml b/OsmAnd-telegram/res/values-cs/strings.xml
index 8972d7f937..cd32a46403 100644
--- a/OsmAnd-telegram/res/values-cs/strings.xml
+++ b/OsmAnd-telegram/res/values-cs/strings.xml
@@ -72,4 +72,6 @@
     Povolen
     Jednotky vzdálenosti
     Vzhled
+    Zásobník logcat
+    Zkontrolovat a sdílet podrobné záznamy aplikace
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-da/strings.xml b/OsmAnd-telegram/res/values-da/strings.xml
index a6e077e7f8..caefa87b94 100644
--- a/OsmAnd-telegram/res/values-da/strings.xml
+++ b/OsmAnd-telegram/res/values-da/strings.xml
@@ -269,4 +269,8 @@
     Sidste svar: %1$s siden
     %1$s siden
     ERR
+    Eksporter
+    Logcat-buffer
+    Kontroller og del detaljerede logfiler for programmet
+    Send rapport
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-de/strings.xml b/OsmAnd-telegram/res/values-de/strings.xml
index 8c12570da5..0767c598d8 100644
--- a/OsmAnd-telegram/res/values-de/strings.xml
+++ b/OsmAnd-telegram/res/values-de/strings.xml
@@ -267,4 +267,8 @@
     Letzte Antwort: vor %1$s
     vor %1$s
     ERR
+    Export
+    Logcat-Puffer
+    Protokolle der Anwendung einsehen und freigeben
+    Bericht senden
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-es-rUS/strings.xml b/OsmAnd-telegram/res/values-es-rUS/strings.xml
index 86bfb79310..259192e6a8 100644
--- a/OsmAnd-telegram/res/values-es-rUS/strings.xml
+++ b/OsmAnd-telegram/res/values-es-rUS/strings.xml
@@ -268,4 +268,8 @@
     Última respuesta: Hace %1$s
     Hace %1$s
     ERR
+    Exportar
+    Búfer de Logcat
+    Comprueba y comparte los registros detallados de la aplicación
+    Enviar informe
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-es/strings.xml b/OsmAnd-telegram/res/values-es/strings.xml
index 63c0fcf19b..d253eabfff 100644
--- a/OsmAnd-telegram/res/values-es/strings.xml
+++ b/OsmAnd-telegram/res/values-es/strings.xml
@@ -250,7 +250,7 @@
     Elige la hora de visualización
     Fecha de Inicio — Fin
     Mensajes guardados
-    Seleccione la zona horaria que desea mostrar en los mensajes de ubicación.
+    Seleccione la zona horaria a mostrar en sus mensajes de ubicación.
     Zona horaria
     Unidades y formatos
     Cambia las unidades de longitud.
@@ -268,4 +268,8 @@
     Última respuesta: hace %1$s
     hace %1$s
     ERR
+    Exportar
+    Búfer de Logcat
+    Selecciona y comparte logs detallados de la app
+    Enviar informe
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-et/strings.xml b/OsmAnd-telegram/res/values-et/strings.xml
index 126490e593..0ddb7950a7 100644
--- a/OsmAnd-telegram/res/values-et/strings.xml
+++ b/OsmAnd-telegram/res/values-et/strings.xml
@@ -267,4 +267,8 @@
     Viimane vastus: %1$s tagasi
     %1$s tagasi
     ERR
+    Ekspordi
+    Logcati puhver
+    Vaata ja jaga rakenduse detailseid logisid
+    Saada ettekanne
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-fa/strings.xml b/OsmAnd-telegram/res/values-fa/strings.xml
index 824443b2c5..092218d713 100644
--- a/OsmAnd-telegram/res/values-fa/strings.xml
+++ b/OsmAnd-telegram/res/values-fa/strings.xml
@@ -38,8 +38,8 @@
     yd
     ft
     mi
-    ک‌م
-    متر
+    km
+    m
     nmi
     min/m
     min/km
@@ -73,4 +73,13 @@
     یکاهای طول را تغییر دهید.
     یکاهای طول
     ظاهر
+    آخرین پاسخ: %1$s پیش
+    آخرین به‌روزرسانی تلگرام: %1$s پیش
+    آخرین پاسخ: %1$s
+    آخرین به‌روزرسانی تلگرام: %1$s
+    خطا
+    ارسال گزارش
+    برون‌برد
+    بافر لاگ‌کت
+    لاگ‌های جزئی برنامه را بررسی و هم‌رسانی کنید
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-fr/strings.xml b/OsmAnd-telegram/res/values-fr/strings.xml
index bc2bbd3d99..4b9d45edfb 100644
--- a/OsmAnd-telegram/res/values-fr/strings.xml
+++ b/OsmAnd-telegram/res/values-fr/strings.xml
@@ -266,4 +266,9 @@
     Cacher les contacts qui ne se sont pas déplacés depuis un temps donné.
     Définissez l\'heure à laquelle les contacts et groupes sélectionnés verront votre position en temps réel.
     OsmAnd connect
+    depuis
+    Buffer Logcat
+    Vérifier et partager les logs détaillés de l\'application
+    Exporter
+    Envoyer le rapport
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-gl/strings.xml b/OsmAnd-telegram/res/values-gl/strings.xml
index e9e462d42a..28f6b19d1a 100644
--- a/OsmAnd-telegram/res/values-gl/strings.xml
+++ b/OsmAnd-telegram/res/values-gl/strings.xml
@@ -268,4 +268,8 @@
     Escolle a zona horaria que desexas amosar nas mensaxes de localización.
     Tempo de caducidade do búfer
     Tempo máximo para almacenar puntos no búfer
+    Exportar
+    Búfer de Logcat
+    Verifica e comparte rexistros detallados da aplicación
+    Enviar denuncia
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-he/strings.xml b/OsmAnd-telegram/res/values-he/strings.xml
index 99c18d4cb2..65da2f52a1 100644
--- a/OsmAnd-telegram/res/values-he/strings.xml
+++ b/OsmAnd-telegram/res/values-he/strings.xml
@@ -268,4 +268,8 @@
     תגובה אחרונה: לפני %1$s
     לפני %1$s
     שגיאה
+    ייצוא
+    מכלא Logcat
+    בדיקה ושיתוף יומני תיעוד מפורטים של היישומים
+    שליחת דיווח
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-hu/strings.xml b/OsmAnd-telegram/res/values-hu/strings.xml
index 8c257bb040..95d655b5cb 100644
--- a/OsmAnd-telegram/res/values-hu/strings.xml
+++ b/OsmAnd-telegram/res/values-hu/strings.xml
@@ -268,4 +268,8 @@
     Utolsó válasz: %1$s
     Ennyivel ezelőtt: %1$s
     HIBA
+    Exportálás
+    Logcat-puffer (hibanapló)
+    Az alkalmazás részletes naplóinak ellenőrzése és megosztása
+    Jelentés küldése
 
\ No newline at end of file
diff --git a/OsmAnd-telegram/res/values-is/strings.xml b/OsmAnd-telegram/res/values-is/strings.xml
new file mode 100644
index 0000000000..a915a2e4f4
--- /dev/null
+++ b/OsmAnd-telegram/res/values-is/strings.xml
@@ -0,0 +1,274 @@
+
+
+    Einingar og snið
+    Setja upp OsmAnd
+    Lykilorð
+    Meðalhæð
+    Fela
+    sendi (%1$d í biðminni)
+    Endursenda staðsetningu
+    Staðsetning…
+    Meðalhraði
+    Athugaðu og deildu nákvæmum atvikaskrám úr forritinu
+    Mílur/metrar
+    Lykilorð í Telegram
+    Deila staðsetningu sem
+    Deila staðsetningu
+    Sía: Stilltu lágmarksfjarlægð frá síðustu staðsetningu þar sem punktur er tekinn í skráningu
+    Til baka
+    Slökkt
+    Engin internettenging
+    Stilla tíma
+    Samþykkja
+    sek
+    %1$d mín
+    Mílur/fet
+    Vélmenni
+    Meðferð persónuupplýsinga í OsmAnd
+    Sendi staðsetningu
+    Tegund milliþjóns (proxy)
+    Breyta einingum sem notaðar eru við lengdarmælingar.
+    Staðsetningaferill
+    Merki OsmAnd
+    Gat ekki bætt við nýju tæki
+    Staða
+    Stillingar milliþjóns (proxy)
+    Gátt
+    Eftir nafni
+    Tímabelti
+    mín/km
+    m
+    Skráning staðsetningar virk
+    Tenging
+    Kílómetrar/metrar
+    Síðasta uppfærsla frá Telegram: Fyrir %1$s síðan
+    Vöktun er óvirk
+    Staða OsmAnd-rakningar
+    OsmAnd-rakning
+    Hópur
+    mín/ml
+    Netþjónn
+    Slökkva á öllu
+    Til baka í OsmAnd
+    Heiti tækis er of langt
+    Loka
+    %1$d klst %2$d mín
+    Mínútur á mílu
+    mín
+    Byrja
+    Vista
+    Landakort og texti
+    Lykilorð
+    %1$s bætt við.
+    Birta í OsmAnd
+    Síðasta tiltæka staðsetning
+    Sent
+    Auðkenningarkóði
+    Leita í tengiliðum
+    Opna OsmAnd
+    Endar
+    Settu inn auðkenningarkóða
+    Staðsetning mín
+    Skrá út
+    Birta á korti
+    Staða deilingar
+    Hætta við
+    Bíð eftir svari frá Telegram
+    Tókst að senda og uppfæra
+    Velja
+    Metrar á sekúndu
+    Heiti tækis
+    Velkomin
+    Hvernig það virkar
+    Stefna
+    Milliþjónn
+    Engin GPS-tenging
+    Senda staðsetningu sem
+    %1$d punktar
+    yd
+    Gera óvirkt
+    Tími á ferðinni
+    Sjómílur á klukkustund (hnútar)
+    %1$d klst
+    Lykill
+    Bakgrunnsvinna
+    Setja upp
+    Bæta við
+    Raða eftir
+    Síðasta svar: %1$s
+    Allt
+    Hæð
+    Birta GPS-punkta
+    Heiti tækis getur ekki verið tómt
+    Leita
+    Eftir vegalengd
+    Sjómílur
+    m/sek
+    Þú ert ekki skráð/ur inn
+    Ræsing
+    Lágmarksnákvæmni skráningar
+    Loka
+    Lágmarkshraði skráninga
+    OsmAnd rakningarþjónusta
+    Telegram
+    Kveikja á \"Staðsetning\"\?
+    Tengdur aðgangur
+    mi
+    Notandanafn
+    Heimild
+    Lengdareiningar
+    GPX-stillingar
+    Nýskráning í Telegram
+    Vegalengd
+    klst
+    km/klst
+    Skrá inn
+    Síðasta svar
+    Deila staðsetningu
+    ft
+    Nafn
+    %1$s síðan
+    Veldu notendur eða hópa til að deila með staðsetningu þinni.
+    Ég er ekki með Telegram-aðgang
+    Vöktun er virk
+    Rennur út
+    Gagnaleynd
+    Notandaaðgangur
+    Virkt
+    Nákvæmni
+    VILL
+    Ekki á ferð
+    Halda áfram
+    Staða
+    Upphafs — Endadagsetning
+    Aftengt
+    sml
+    Uppfæra
+    Settu inn lykilorð
+    Deila
+    Logcat biðminni
+    Settu inn símanúmer
+    Mínútur á kílómetra
+    Hætta
+    Útlit
+    Dagsetning
+    Síðar
+    Stefna
+    Hraðaeining
+    Landakort
+    Sía: Engin skráning punkta fyrir neðan valinn hraða
+    í %1$s
+    Leit: Hópur eða tengiliður
+    hnútar
+    km
+    Tímalína
+    Vistuð skilaboð
+    GPS-punktar
+    Virkja
+    Lágmarksfjarlægð skráninga
+    Síðasta uppfærsla frá Telegram
+    Safnað
+    Skrái út
+    Mílur/yardar
+    Veldu tíma sem á að birta
+    Auðkenni
+    Kílómetrar á klukkustund
+    Síðasta svar: Fyrir %1$s síðan
+    Tengist internetinu
+    Senda skýrslu
+    Flytja út
+    Mílur á klukkustund
+    Í lagi
+    Virkja
+    Persónuverndarstefna Telegram
+    Virkt spjall
+    Senda staðsetningu mína
+    Stungið upp á
+    Fara í stillingar
+    mi/klst
+    Deiling í bakgrunni
+    Deiling: %1$s
+    Síðasta uppfærsla frá Telegram: %1$s
+    Byrja
+    Raða
+    Engin gögn
+    Eftir hópi
+    Bæta við tæki
+    Texti
+    Stillingar
+    Birta notendur á kortinu
+    Tengt
+    Símanúmer
+    Upphafsdagsetning
+    Skilgreindu einingu fyrir hraða.
+    síðan
+    Settu inn kóða
+    Gera vöktun óvirka
+    Forritið hefur ekki heimildir til að nota staðsetningargögn.
+    Lokadagsetning
+    Bakgrunnshamur
+    OsmAnd-rakning keyrir í bakgrunni á meðan slökkt er á skjá.
+    Ekki fundist ennþá
+    Móttók GPX-punkta: %1$s
+    Deili staðsetningu
+    Hvernig á að slökkva á OsmAnd-rakningu úr Telegram
+    Ekki sent ennþá
+    OsmAnd nettengdur GPS-rekjari
+    Skrá út úr OsmAnd-rakningu\?
+    Kveikt er á deilingu (slökkva)
+    Hvernig á að slökkva á OsmAnd-rakningu úr Telegram
+    Virkjaðu \"Staðsetning\" í stillingunum stýrikerfisins
+    Tengiliðir og hópar sem deila staðsetningu til þín.
+    Tengstu við internetið til að geta skráð þig til fulls út úr Telegram.
+    Með því að smella á \'Halda áfram\', samþykkir þú ákvæði og skilyrði varðandi gagnaleynd hjá Telegram og OsmAnd.
+    Gildistími biðminnis
+    Tími deilingar
+    Veldu hvernig skilaboð með staðsetningu þinni líti út.
+    Þú þarft fyrst að setja upp ókeypis eða greidda útgáfu OsmAnd
+    Slekkur á deilingu staðsetningar til allra valinna spjalla (%1$d).
+    Slökkva á deilingu staðsetningar
+    Settu inn nafn tengiliðar eða hóps
+    Tímalína er eiginleiki sem er núna aðgengilegur ókeypis.
+    Þú þarft skráðan Telegram-aðgang og símanúmer
+    Telegram hefur sent þér kóða fyrir OsmAnd til að skrá þig inn í notandaaðganginn þinn.
+    Veldu hvaða útgáfu OsmAnd þú vilt nota
+    Gera alla deilingu óvirka
+    Uppfærðu OsmAnd til að skoða gögn á kortinu
+    Veldu tímabelti til birtingar í staðsetningarskilaboðum þínum.
+    Veldu eina af staðsetningarþjónustunum til að deila staðsetningu þinni.
+    Stilla tímabil þar sem allir eru sýnilegir
+    Veldu þá útgáfu OsmAnd sem OsmAnd-rakningin notar til að birta staðsetningar.
+    Endilega settu upp Telegram og skráðu notandaaðgang.
+    Settu inn Telegram-símanúmerið þitt á alþjóðlegu sniði
+    Virkja vöktun til að vista allar staðsetningar í aðgerðaferli.
+    Tímabil þar sem allir eru sýnilegir
+    Stilla minnsta bil á milli deilingar staðsetningar.
+    Hámarkstími sem punktar eru geymdir í biðminninu
+    Þú þarft skráðan Telegram-aðgang til að geta notað deilingu staðsetningar.
+    Símanúmer á alþjóðlegu sniði
+    Veldu þá útgáfu OsmAnd þar sem tengiliðir verða birtir á kortinu.
+    Tengjast OsmAnd
+    Birta fjölda safnaðra og sendra GPS-punkta.
+    Telegram (skilaboðaforritið) er notað til að tengjast og eiga í samskiptum við fólk.
+    þá geturðu notað þetta forrit.
+    Gefðu nýja tækinu þínu nafn að hámarki 200 stafir.
+    Við erum ekki með söfnuð gögn fyrir valinn dag
+    Leita í öllum þínum hópum og tengiliðum.
+    Sía: Stilltu lágmarksnákvæmni punkts til að hann sé tekinn í skráningu
+    Fela tengiliði sem ekki hafa hreyfst á tilteknu tímabili.
+    Síðasta skiptið sem tengiliður hreyfðist.
+    Síðasta uppfærða staðsetning:
+    Veldu nafn sem þú hefur ekki þegar notað
+    Ekki mögulegt að senda á Telegram-spjöll:
+    Til að afturkalla heimildir til deilingar á staðsetningu, opnaðu Telegram, farðu í Stillingar → Gagnaleynd og öryggi → Setur, og bittu enda á setu OsmAnd-rakningar.
+    Þú getur útbúið og skoðað auðkenningu tækis (device ID) í Telegram-biðlaraforritinu með því að nota %1$s spjallvélmennið. %2$s
+    Ertu viss að þú viljir skrá þig út úr OsmAnd-rakningu þannig að þú getir ekki lengur deilt þinni staðsetningu eða séð staðsetningu annarra\?
+    Slokktu á bestun rafhlöðunýtingar fyrir OsmAnd-rakningu svo ekki slökkni á henni þegar forritið fer í bakgrunnsham (t.d. slökkt er á skjá).
+    OsmAnd-rakning er eitt af biðlaraforritunum sem nota opna Telegram-kerfið. Tengiliðirnir þínir geta notað eitthvað annað Telegram-biðlaraforrit.
+    OsmAnd-rakning gerir þér kleift að deila staðsetningu þinni og að sjá aðra í OsmAnd.

Forritið notar Telegram API-forritsviðmótið, þannig að þú verður að vera með Telegram-aðgang.
+ Ef þú ætlar að tengja mörg tæki við einn Telegram-notandaaðgang, þarftu að nota annað tæki til að deila staðsetningunni þinni. + Rauntíma + Breyta bestunarstillingum rafhlöðu til að auka stöðugleika í deilingu staðsetningar. + Veldu tímann sem valdir tengiliðir og hópar munu sjá staðsetningu þína í rauntíma. + Rauntíma núna +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-it/strings.xml b/OsmAnd-telegram/res/values-it/strings.xml index 15986c05a0..05cf56a8da 100644 --- a/OsmAnd-telegram/res/values-it/strings.xml +++ b/OsmAnd-telegram/res/values-it/strings.xml @@ -265,4 +265,6 @@ Ultima risposta: %1$s Ultimo aggiornamento da Telegram: %1$s ERR + Controlla e condividi i log dettagliati dell\'applicazione + Logcat buffer \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-ja/strings.xml b/OsmAnd-telegram/res/values-ja/strings.xml index 71c5af7daf..1829c1fcc9 100644 --- a/OsmAnd-telegram/res/values-ja/strings.xml +++ b/OsmAnd-telegram/res/values-ja/strings.xml @@ -219,8 +219,8 @@ 国際形式でTelegramを利用する端末の電話番号を入力してください(日本の場合+81を先頭につけて電話番号最初の0を除いた番号を入力) ようこそ ヤード - フィート - マイル + ft + mi km m 海里 diff --git a/OsmAnd-telegram/res/values-nb/strings.xml b/OsmAnd-telegram/res/values-nb/strings.xml index 6b42727c8a..5b6d88b4af 100644 --- a/OsmAnd-telegram/res/values-nb/strings.xml +++ b/OsmAnd-telegram/res/values-nb/strings.xml @@ -179,7 +179,7 @@ Oppsyn er påskrudd Oppsyn er ikke aktivert Tid i bevegelse - Gjennomsnittlig høyde + Gjennomsnittshøyde Gjennomsnittsfart Vis i OsmAnd Sluttdato @@ -267,4 +267,8 @@ Siste respons: %1$s siden %1$s siden FEIL + Logcat-mellomlager + Eksporter + Sjekk og del detaljert loggføring fra programmet + Send rapport \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-nl/strings.xml b/OsmAnd-telegram/res/values-nl/strings.xml index b77535908e..ebddf36454 100644 --- a/OsmAnd-telegram/res/values-nl/strings.xml +++ b/OsmAnd-telegram/res/values-nl/strings.xml @@ -257,4 +257,8 @@ Afstand eenheden Definieer de eenheid voor snelheid. Eenheid van snelheid + Stuur rapport + Exporteer naar OSM + Logcat buffer + Controleer en deel gedetailleerde logs van de app \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pl/strings.xml b/OsmAnd-telegram/res/values-pl/strings.xml index 22e12de229..f5f13c5c0d 100644 --- a/OsmAnd-telegram/res/values-pl/strings.xml +++ b/OsmAnd-telegram/res/values-pl/strings.xml @@ -267,4 +267,8 @@ ERR Ostatnia odpowiedź: %1$s Ostatnia odpowiedź: %1$s temu + Eksportuj + Bufor katalogu dziennika + Sprawdzanie i udostępnianie szczegółowych logów aplikacji + Wyślij raport \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pt-rBR/strings.xml b/OsmAnd-telegram/res/values-pt-rBR/strings.xml index c1aa3b8e55..d792f9748f 100644 --- a/OsmAnd-telegram/res/values-pt-rBR/strings.xml +++ b/OsmAnd-telegram/res/values-pt-rBR/strings.xml @@ -267,4 +267,8 @@ Última resposta: %1$s atrás %1$s atrás ERR + Exportar + Buffer de Logcat + Verifique e compartilhe registros detalhados do aplicativo + Enviar o relatório \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pt-rPT/strings.xml b/OsmAnd-telegram/res/values-pt-rPT/strings.xml index 6cbbce3ee6..3675efa298 100644 --- a/OsmAnd-telegram/res/values-pt-rPT/strings.xml +++ b/OsmAnd-telegram/res/values-pt-rPT/strings.xml @@ -267,4 +267,8 @@ Última resposta: %1$s Última atualização do Telegram: %1$s ERR + Exportar + Buffer de logcat + Verifique e compartilhe registos detalhados da app + Enviar o relatório \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pt/strings.xml b/OsmAnd-telegram/res/values-pt/strings.xml index 8b114893d7..9a516699e2 100644 --- a/OsmAnd-telegram/res/values-pt/strings.xml +++ b/OsmAnd-telegram/res/values-pt/strings.xml @@ -108,7 +108,7 @@ Ainda não encontrado Reenvie o local Última localização disponível - Status de compartilhamento + Estado de compartilhamento Compartilhamento: %1$s Ativado Sem conexão GPS @@ -163,7 +163,7 @@ Por favor, instale o Telegram e configure uma conta. Então pode usar esta app. Todos - Desativado + Desligado Precisa de uma conta e número de telefone registados no Telegram Não tenho uma conta do Telegram Digite o número de telefone @@ -204,7 +204,7 @@ A app não tem permissão para acessar os dados de localização. Por favor, ligue \"Localização\" nas configurações do sistema Selecione um dos provedores de localização para compartilhar sua localização. - Modo em segundo plano + Modo de fundo OsmAnd Tracker é executado em segundo plano com o ecrã desligado. Distância Compartilhar localização @@ -230,15 +230,15 @@ m/s km/h mph - Quilômetros por hora + Quilómetros por hora Milhas por hora Metros por segundo - Minutos por quilômetro + Minutos por quilómetro Minutos por milha Milhas náuticas por hora (nó) Milhas/pés Milhas/jardas - Quilômetros/metros + Quilómetros/metros Milhas náuticas Milhas/metros h @@ -252,7 +252,7 @@ Selecione o fuso horário a mostrar nas suas mensagens de localização. Fuso horário Unidades e formatos - Alterar unidade de distância. + Alterar a unidade de medida de distância. Unidades de comprimento Definir unidade de velocidade. Unidade de velocidade @@ -267,4 +267,8 @@ Última resposta: %1$s Última atualização do Telegram: %1$s ERR + Enviar o relatório + Exportar + Buffer de logcat + Verifique e compartilhe registos detalhados da app \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-ru/strings.xml b/OsmAnd-telegram/res/values-ru/strings.xml index daff0e950b..e12c0245b5 100644 --- a/OsmAnd-telegram/res/values-ru/strings.xml +++ b/OsmAnd-telegram/res/values-ru/strings.xml @@ -75,7 +75,7 @@ По расстоянию По имени По группе - Сортировать + Сортировка Сортировать по Отстановить все Выход @@ -267,4 +267,8 @@ Последнее обновление от Telegram: %1$s назад Последний ответ: %1$s Последнее обновление от Telegram: %1$s + Экспорт + Буфер Logcat + Проверьте и поделитесь подробными журналами приложения + Отправить отчёт \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-sc/strings.xml b/OsmAnd-telegram/res/values-sc/strings.xml index 9846061b82..82a4e8aade 100644 --- a/OsmAnd-telegram/res/values-sc/strings.xml +++ b/OsmAnd-telegram/res/values-sc/strings.xml @@ -268,4 +268,8 @@ Ùrtima risposta: %1$s a como %1$s a como ERR + Esporta + Buffer de Logcat + Verìfica e cumpartzi sos registros de s\'aplicatzione fatos a sa minuda + Imbia resumu \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-sr/strings.xml b/OsmAnd-telegram/res/values-sr/strings.xml index 52c261dbe5..4220079b9b 100644 --- a/OsmAnd-telegram/res/values-sr/strings.xml +++ b/OsmAnd-telegram/res/values-sr/strings.xml @@ -267,4 +267,8 @@ Последњи одговор: %1$ Последње ажурирање из Телеграма: %1$ Грешка + Извези + Logcat бафер + Проверите и поделите детаљне записе апликације + Пошаљи извештај \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-tr/strings.xml b/OsmAnd-telegram/res/values-tr/strings.xml index 086306b609..2ff160a4a4 100644 --- a/OsmAnd-telegram/res/values-tr/strings.xml +++ b/OsmAnd-telegram/res/values-tr/strings.xml @@ -233,7 +233,7 @@ OsmAnd Tracker, ekran kapalıyken arka planda çalışır. Konumu paylaş Konum paylaşılıyor - OsmAnd Tracker servisi + OsmAnd Tracker hizmeti OsmAnd logosu Önce OsmAnd\'ın ücretsiz veya ücretli sürümünü yüklemeniz gerekmektedir OsmAnd\'ı yükle @@ -267,4 +267,8 @@ Son cevap: %1$s önce %1$s önce HATA + Dışa aktar + Logcat tamponu + Uygulamanın ayrıntılı günlük kayıtlarına göz atın ve paylaşın + Rapor gönder \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-tzm/strings.xml b/OsmAnd-telegram/res/values-tzm/strings.xml new file mode 100644 index 0000000000..e2a8f81966 --- /dev/null +++ b/OsmAnd-telegram/res/values-tzm/strings.xml @@ -0,0 +1,52 @@ + + + Aɣul + Bḍu + Ssekcem uṭṭun n utilifun + Akk + Abut + Usrid + g %1$s + Amiḍan + Rgel + Tarabbut + aya + Ffeɣ + Fren s + Fren + S trabbut + S isem + Isem + Bḍu adɣar am + Rnu allal + Ḥḍu + Addad + Ddu ɣer tesɣal + Azan n udɣar + Ssentel + Rnu + Taleqqemt tameggarut seg Tiligṛam + Takaṛḍa d uḍṛiṣ + Aḍṛiṣ + Takaṛḍa + Azen adɣar am + Asakud n usenti + Asakud + Ssedɣi + Tiligṛam + WAX + Rzu + Tanila + Tinnutla + Apṛuksi + Tisɣal n Upṛuksi + Izdey + Anaw n upṛuksi + Taguri n uzray + Tasarut + Tisɣal n GPX + Stey + Ssenti + Aɣul ɣer OsmAnd + %1$s aya + \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-uk/strings.xml b/OsmAnd-telegram/res/values-uk/strings.xml index 1ff2bebb09..8e706abe85 100644 --- a/OsmAnd-telegram/res/values-uk/strings.xml +++ b/OsmAnd-telegram/res/values-uk/strings.xml @@ -115,10 +115,10 @@ Ви не увійшли до системи Продовжити Скасувати - Налаштування + Параметри Застосунок не має дозволу до отримання даних позиціювання. Будь ласка, увімкніть «Позиціювання» у системних налаштуваннях - Фоновий режим + Режим тла OsmAnd Tracker працює у фоновому режимі з вимкненим екраном. Відстань Поділитися позицією @@ -267,4 +267,8 @@ Остання відповідь: %1$s тому %1$s тому ПМЛК + Експорт + Буфер logcat + Переглянути та надіслати докладний журнал застосунку + Надіслати звіт \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-zh-rTW/strings.xml b/OsmAnd-telegram/res/values-zh-rTW/strings.xml index f1f06a0daa..7739c65184 100644 --- a/OsmAnd-telegram/res/values-zh-rTW/strings.xml +++ b/OsmAnd-telegram/res/values-zh-rTW/strings.xml @@ -270,4 +270,8 @@ 最後回應:%1$s 前 %1$s 前 ERR + 傳送報告 + 匯出 + Logcat 緩衝 + 檢查及分享應用程式的詳細紀錄 \ No newline at end of file diff --git a/OsmAnd-telegram/res/values/dimens.xml b/OsmAnd-telegram/res/values/dimens.xml index 7f4942dc3e..60976ff822 100644 --- a/OsmAnd-telegram/res/values/dimens.xml +++ b/OsmAnd-telegram/res/values/dimens.xml @@ -27,6 +27,7 @@ 89dp 48dp + 44dp 42dp 56dp diff --git a/OsmAnd-telegram/res/values/strings.xml b/OsmAnd-telegram/res/values/strings.xml index 2b5eca334e..8bccf957ce 100644 --- a/OsmAnd-telegram/res/values/strings.xml +++ b/OsmAnd-telegram/res/values/strings.xml @@ -1,5 +1,9 @@ + Send report + Check and share detailed logs of the app + Logcat buffer + Export ERR Last update from Telegram: %1$s Last response: %1$s diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt index ee6bbb29d4..b6927dc610 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt @@ -3,16 +3,20 @@ package net.osmand.telegram import android.app.Application import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.NetworkInfo import android.os.Build import android.os.Handler +import net.osmand.PlatformUtil +import net.osmand.telegram.ui.TrackerLogcatActivity import net.osmand.telegram.helpers.* import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener import net.osmand.telegram.notifications.NotificationHelper import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.UiUtils +import java.io.File class TelegramApplication : Application() { @@ -200,4 +204,33 @@ class TelegramApplication : Application() { fun runInUIThread(action: (() -> Unit), delay: Long) { uiHandler.postDelayed(action, delay) } + + fun sendCrashLog(file: File) { + val intent = Intent(Intent.ACTION_SEND) + intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("crash@osmand.net")) + intent.putExtra(Intent.EXTRA_STREAM, AndroidUtils.getUriForFile(this, file)) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = "vnd.android.cursor.dir/email" + intent.putExtra(Intent.EXTRA_SUBJECT, "OsmAnd bug") + val text = StringBuilder() + text.append("\nDevice : ").append(Build.DEVICE) + text.append("\nBrand : ").append(Build.BRAND) + text.append("\nModel : ").append(Build.MODEL) + text.append("\nProduct : ").append(Build.PRODUCT) + text.append("\nBuild : ").append(Build.DISPLAY) + text.append("\nVersion : ").append(Build.VERSION.RELEASE) + text.append("\nApp : ").append(getString(R.string.app_name_short)) + try { + val info = packageManager.getPackageInfo(packageName, 0) + if (info != null) { + text.append("\nApk Version : ").append(info.versionName).append(" ").append(info.versionCode) + } + } catch (e: PackageManager.NameNotFoundException) { + PlatformUtil.getLog(TrackerLogcatActivity::class.java).error("", e) + } + intent.putExtra(Intent.EXTRA_TEXT, text.toString()) + val chooserIntent = Intent.createChooser(intent, getString(R.string.send_report)) + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(chooserIntent) + } } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt index 4ff84ca542..ee05034cb7 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt @@ -18,7 +18,6 @@ import net.osmand.telegram.helpers.TelegramHelper import net.osmand.telegram.helpers.TelegramHelper.* import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.utils.AndroidUtils -import net.osmand.telegram.utils.OsmandLocationUtils import org.drinkless.td.libcore.telegram.TdApi import java.util.* @@ -377,7 +376,10 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis Log.d(PlatformUtil.TAG, "Send live location error: $code - $message") when (messageType) { TelegramHelper.MESSAGE_TYPE_TEXT -> shareInfo.pendingTdLibText-- - TelegramHelper.MESSAGE_TYPE_MAP -> shareInfo.pendingTdLibMap-- + TelegramHelper.MESSAGE_TYPE_MAP -> { + shareInfo.pendingTdLibMap-- + shareInfo.currentMapMessageId = -1L + } } } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt index d8ec28a9c0..a6a2377118 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt @@ -110,7 +110,7 @@ private const val PROXY_ENABLED = "proxy_enabled" private const val PROXY_PREFERENCES_KEY = "proxy_preferences" private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes -private const val WAITING_TDLIB_TIME = 3 // 3 seconds +private const val WAITING_TDLIB_TIME = 7 // 7 seconds private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes @@ -304,27 +304,18 @@ class TelegramSettings(private val app: TelegramApplication) { fun prepareForSharingNewMessages() { shareChatsInfo.forEach { (_, shareInfo) -> - prepareForSharingNewMessages(shareInfo) + shareInfo.resetMessagesInfo() } } fun prepareForSharingNewMessages(chatsIds: List) { chatsIds.forEach { shareChatsInfo[it]?.also { shareInfo -> - prepareForSharingNewMessages(shareInfo) + shareInfo.resetMessagesInfo() } } } - fun prepareForSharingNewMessages(shareInfo: ShareChatInfo) { - shareInfo.pendingTdLibText = 0 - shareInfo.pendingTdLibMap = 0 - shareInfo.currentTextMessageId = -1L - shareInfo.currentMapMessageId = -1L - shareInfo.pendingTextMessage = false - shareInfo.pendingMapMessage = false - } - fun getChatLivePeriod(chatId: Long) = shareChatsInfo[chatId]?.livePeriod fun getChatsShareInfo() = shareChatsInfo @@ -540,14 +531,24 @@ class TelegramSettings(private val app: TelegramApplication) { if (initTime && initSending) { initializing = true } else { + var waitingTimeError = false val maxWaitingTime = WAITING_TDLIB_TIME * MAX_MESSAGES_IN_TDLIB_PER_CHAT * max(1, chatsCount) - val textSharingError = !shareInfo.lastTextMessageHandled && currentTime - shareInfo.lastSendTextMessageTime > maxWaitingTime - val mapSharingError = !shareInfo.lastMapMessageHandled && currentTime - shareInfo.lastSendMapMessageTime > maxWaitingTime - if (shareInfo.hasSharingError - || (shareTypeValue == SHARE_TYPE_MAP_AND_TEXT && (textSharingError || mapSharingError)) - || textSharingError && (shareTypeValue == SHARE_TYPE_TEXT) - || mapSharingError && (shareTypeValue == SHARE_TYPE_MAP) - ) { + val textSharingWaitingTime = currentTime - shareInfo.lastSendTextMessageTime + val mapSharingWaitingTime = currentTime - shareInfo.lastSendMapMessageTime + val textSharingError = !shareInfo.lastTextMessageHandled && textSharingWaitingTime > maxWaitingTime + val mapSharingError = !shareInfo.lastMapMessageHandled && mapSharingWaitingTime > maxWaitingTime + if ((shareTypeValue == SHARE_TYPE_MAP_AND_TEXT && (textSharingError || mapSharingError)) + || textSharingError && (shareTypeValue == SHARE_TYPE_TEXT) + || mapSharingError && (shareTypeValue == SHARE_TYPE_MAP)) { + waitingTimeError = true + log.debug("Send chats error for share type \"$shareTypeValue\"" + + "\nMax waiting time: ${maxWaitingTime}s" + + "\nLast text message handled: ${shareInfo.lastTextMessageHandled}" + + "\nText sharing waiting time: ${textSharingWaitingTime}s" + + "\nLast map message handled: ${shareInfo.lastMapMessageHandled}" + + "\nMap sharing waiting time: ${mapSharingWaitingTime}s") + } + if (shareInfo.hasSharingError || waitingTimeError) { sendChatsErrors = true locationTime = max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime) chatsIds.add(shareInfo.chatId) @@ -1487,6 +1488,27 @@ class TelegramSettings(private val app: TelegramApplication) { fun isPendingMapMessagesLimitReached() = pendingTdLibMap >= MAX_MESSAGES_IN_TDLIB_PER_CHAT + fun resetMessagesInfo() { + resetTextMessageInfo() + resetMapMessageInfo() + } + + fun resetTextMessageInfo() { + pendingTdLibText = 0 + currentTextMessageId = -1L + pendingTextMessage = false + } + + fun resetMapMessageInfo() { + pendingTdLibMap = 0 + currentMapMessageId = -1L + pendingMapMessage = false + } + + fun isTextMessageIdPresent() = currentTextMessageId != -1L + + fun isMapMessageIdPresent() = currentMapMessageId != -1L + companion object { internal const val CHAT_ID_KEY = "chatId" diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt index 8aa7d29856..35a397f4f5 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt @@ -138,7 +138,7 @@ class ShareLocationHelper(private val app: TelegramApplication) { } } if (pendingMessagesLimitReached && checkNetworkTypeAllowed) { - checkNetworkType() + updateNetworkType() } } @@ -167,7 +167,7 @@ class ShareLocationHelper(private val app: TelegramApplication) { app.locationMessages.getBufferedTextMessagesForChat(chatId).take(MAX_MESSAGES_IN_TDLIB_PER_CHAT).forEach { if (!shareInfo.isPendingTextMessagesLimitReached()) { if (it.deviceName.isEmpty()) { - if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) { + if (!shareInfo.pendingTextMessage && shareInfo.isTextMessageIdPresent()) { val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, it, app) app.telegramHelper.editTextLocation(shareInfo, content) app.locationMessages.removeBufferedMessage(it) @@ -180,8 +180,12 @@ class ShareLocationHelper(private val app: TelegramApplication) { app.locationMessages.getBufferedMapMessagesForChat(chatId).take(MAX_MESSAGES_IN_TDLIB_PER_CHAT).forEach { if (!shareInfo.isPendingMapMessagesLimitReached()) { if (it.deviceName.isEmpty()) { - if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) { - app.telegramHelper.editMapLocation(shareInfo, it) + if (!shareInfo.pendingMapMessage) { + if (shareInfo.isMapMessageIdPresent()) { + app.telegramHelper.editMapLocation(shareInfo, it) + } else { + app.telegramHelper.sendNewMapLocation(shareInfo, it) + } app.locationMessages.removeBufferedMessage(it) } } else { @@ -279,7 +283,7 @@ class ShareLocationHelper(private val app: TelegramApplication) { } } if (pendingMessagesLimitReached) { - checkNetworkType() + updateNetworkType() } } @@ -347,7 +351,7 @@ class ShareLocationHelper(private val app: TelegramApplication) { } } - fun checkNetworkType(){ + fun updateNetworkType(){ if (app.isInternetConnectionAvailable) { val networkType = when { app.isWifiConnected -> TdApi.NetworkTypeWiFi() diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index e4d5188a2b..941e4a8aa4 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -776,6 +776,7 @@ class TelegramHelper private constructor() { client?.send(TdApi.CreatePrivateChat(userId, false)) { obj -> when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { + log.debug("createPrivateChatWithUser ERROR $obj") val error = obj as TdApi.Error if (error.code != IGNORED_ERROR_CODE) { shareInfo.hasSharingError = true @@ -839,7 +840,7 @@ class TelegramHelper private constructor() { } fun stopSendingLiveLocationToChat(shareInfo: ShareChatInfo) { - if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) { + if (!shareInfo.isMapMessageIdPresent() && shareInfo.chatId != -1L) { shareInfo.lastSendMapMessageTime = (System.currentTimeMillis() / 1000).toInt() client?.send( TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj -> @@ -969,7 +970,7 @@ class TelegramHelper private constructor() { val messageType = if (isBot) MESSAGE_TYPE_BOT else MESSAGE_TYPE_TEXT when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { - log.debug("handleTextLocationMessageUpdate - ERROR") + log.debug("handleTextLocationMessageUpdate - ERROR $obj") val error = obj as TdApi.Error if (error.code != IGNORED_ERROR_CODE) { shareInfo.hasSharingError = true diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt index f3391246b4..fbc3725ec1 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt @@ -15,7 +15,6 @@ import android.widget.TextView import androidx.appcompat.widget.ListPopupWindow import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import net.osmand.Location import net.osmand.data.LatLon import net.osmand.telegram.R @@ -99,7 +98,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage mainView.findViewById(R.id.swipe_refresh).apply { setOnRefreshListener { - app.shareLocationHelper.checkNetworkType() + app.shareLocationHelper.updateNetworkType() app.telegramHelper.scanChatsHistory() updateList() isRefreshing = false diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt index a5bf5993e9..447275815e 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt @@ -213,6 +213,12 @@ class SettingsDialogFragment : BaseDialogFragment() { DisconnectTelegramBottomSheet.showInstance(childFragmentManager) } + mainView.findViewById(R.id.logcat_row).setOnClickListener { + val intent = Intent(activity, TrackerLogcatActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + app.startActivity(intent) + } + return mainView } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt index ef080fe40d..3dd9968f1c 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt @@ -72,7 +72,7 @@ class SharingStatusBottomSheet : DialogFragment() { if (sharingStatusType.canResendLocation) { if (i == 0) { setOnClickListener { - app.shareLocationHelper.checkNetworkType() + app.shareLocationHelper.updateNetworkType() app.settings.prepareForSharingNewMessages(sharingStatus.chatsIds) app.shareLocationHelper.checkAndSendBufferMessages() app.forceUpdateMyLocation() diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/TrackerLogcatActivity.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/TrackerLogcatActivity.kt new file mode 100644 index 0000000000..ee62ab4407 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/TrackerLogcatActivity.kt @@ -0,0 +1,271 @@ +package net.osmand.telegram.ui + +import android.os.AsyncTask +import android.os.Bundle +import android.view.* +import android.widget.ProgressBar +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import net.osmand.PlatformUtil +import net.osmand.telegram.R +import net.osmand.telegram.TelegramApplication +import java.io.* +import java.lang.ref.WeakReference +import java.util.* + +class TrackerLogcatActivity : AppCompatActivity() { + private var logcatAsyncTask: LogcatAsyncTask? = null + private val logs: MutableList = ArrayList() + private var adapter: LogcatAdapter? = null + private val LEVELS = arrayOf("D", "I", "W", "E") + private var filterLevel = 1 + private lateinit var recyclerView: RecyclerView + + override fun onCreate(savedInstanceState: Bundle?) { + val app: TelegramApplication = getApplication() as TelegramApplication + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_tracker_logcat) + + val toolbar = findViewById(R.id.toolbar).apply { + navigationIcon = app.uiUtils.getThemedIcon(R.drawable.ic_arrow_back) + setNavigationOnClickListener { onBackPressed() } + } + setSupportActionBar(toolbar) + setupIntermediateProgressBar() + + adapter = LogcatAdapter() + recyclerView = findViewById(R.id.recycler_view) as RecyclerView + recyclerView!!.layoutManager = LinearLayoutManager(this) + recyclerView!!.adapter = adapter + } + + protected fun setupIntermediateProgressBar() { + val progressBar = ProgressBar(this) + progressBar.visibility = View.GONE + progressBar.isIndeterminate = true + val supportActionBar = supportActionBar + if (supportActionBar != null) { + supportActionBar.setDisplayShowCustomEnabled(true) + supportActionBar.customView = progressBar + setSupportProgressBarIndeterminateVisibility(false) + } + } + + override fun setSupportProgressBarIndeterminateVisibility(visible: Boolean) { + val supportActionBar = supportActionBar + if (supportActionBar != null) { + supportActionBar.customView.visibility = if (visible) View.VISIBLE else View.GONE + } + } + + override fun onResume() { + super.onResume() + startLogcatAsyncTask() + } + + override fun onPause() { + super.onPause() + stopLogcatAsyncTask() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val app: TelegramApplication = applicationContext as TelegramApplication + val share: MenuItem = menu.add(0, SHARE_ID, 0, R.string.shared_string_export) + share.icon = app.uiUtils.getThemedIcon(R.drawable.ic_action_share) + share.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + val level = menu.add(0, LEVEL_ID, 0, "") + level.title = getFilterLevel() + level.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + return super.onCreateOptionsMenu(menu) + } + + private fun getFilterLevel(): String { + return "*:" + LEVELS[filterLevel] + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val itemId = item.itemId + when (itemId) { + android.R.id.home -> { + finish() + return true + } + LEVEL_ID -> { + filterLevel++ + if (filterLevel >= LEVELS.size) { + filterLevel = 0 + } + item.title = getFilterLevel() + stopLogcatAsyncTask() + logs.clear() + adapter!!.notifyDataSetChanged() + startLogcatAsyncTask() + return true + } + SHARE_ID -> { + startSaveLogsAsyncTask() + return true + } + } + return false + } + + private fun startSaveLogsAsyncTask() { + val saveLogsAsyncTask = SaveLogsAsyncTask(this, logs) + saveLogsAsyncTask.execute() + } + + private fun startLogcatAsyncTask() { + logcatAsyncTask = LogcatAsyncTask(this, getFilterLevel()) + logcatAsyncTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + + private fun stopLogcatAsyncTask() { + if (logcatAsyncTask != null && logcatAsyncTask!!.status == AsyncTask.Status.RUNNING) { + logcatAsyncTask!!.cancel(false) + logcatAsyncTask!!.stopLogging() + } + } + + private inner class LogcatAdapter : RecyclerView.Adapter() { + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(viewGroup.context) + val itemView = inflater.inflate(R.layout.item_description_long, viewGroup, false) as TextView + itemView.gravity = Gravity.CENTER_VERTICAL + return LogViewHolder(itemView) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is LogViewHolder) { + val log = getLog(position) + holder.logTextView.text = log + } + } + + override fun getItemCount(): Int { + return logs.size + } + + private fun getLog(position: Int): String { + return logs[position] + } + + private inner class LogViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val logTextView: TextView = itemView.findViewById(R.id.description) + } + } + + class SaveLogsAsyncTask internal constructor(logcatActivity: TrackerLogcatActivity, logs: Collection) : AsyncTask() { + private val logcatActivity: WeakReference + private val logs: Collection + + override fun onPreExecute() { + val activity = logcatActivity.get() + activity?.setSupportProgressBarIndeterminateVisibility(true) + } + + override fun doInBackground(vararg voids: Void?): File { + val app: TelegramApplication = logcatActivity.get()?.applicationContext as TelegramApplication + val file = File(app.getExternalFilesDir(null), LOGCAT_PATH) + try { + if (file.exists()) { + file.delete() + } + val stringBuilder = StringBuilder() + for (log in logs) { + stringBuilder.append(log) + stringBuilder.append("\n") + } + if (file.parentFile.canWrite()) { + val writer = BufferedWriter(FileWriter(file, true)) + writer.write(stringBuilder.toString()) + writer.close() + } + } catch (e: Exception) { + log.error(e) + } + return file + } + + override fun onPostExecute(file: File?) { + val activity = logcatActivity.get() + if (activity != null && file != null) { + val app: TelegramApplication = activity.applicationContext as TelegramApplication + activity.setSupportProgressBarIndeterminateVisibility(false) + app.sendCrashLog(file) + } + } + + init { + this.logcatActivity = WeakReference(logcatActivity) + this.logs = logs + } + } + + class LogcatAsyncTask internal constructor(logcatActivity: TrackerLogcatActivity?, filterLevel: String) : AsyncTask() { + private var processLogcat: Process? = null + private val logcatActivity: WeakReference + private val filterLevel: String + + override fun doInBackground(vararg voids: Void?): Void? { + try { + val filter = android.os.Process.myPid().toString() + val command = arrayOf("logcat", filterLevel, "--pid=$filter", "-T", MAX_BUFFER_LOG.toString()) + processLogcat = Runtime.getRuntime().exec(command) + val bufferedReader = BufferedReader(InputStreamReader(processLogcat?.inputStream)) + var line: String? + while (bufferedReader.readLine().also { line = it } != null && logcatActivity.get() != null) { + if (isCancelled) { + break + } + publishProgress(line) + } + stopLogging() + } catch (e: IOException) { // ignore + } catch (e: Exception) { + log.error(e) + } + return null + } + + override fun onProgressUpdate(vararg values: String?) { + if (values.size > 0 && !isCancelled) { + val activity = logcatActivity.get() + if (activity != null) { + val autoscroll = !activity.recyclerView!!.canScrollVertically(1) + for (s in values) { + if (s != null) { + activity.logs.add(s) + } + } + activity.adapter!!.notifyDataSetChanged() + if (autoscroll) { + activity.recyclerView!!.scrollToPosition(activity.logs.size - 1) + } + } + } + } + + fun stopLogging() { + if (processLogcat != null) { + processLogcat!!.destroy() + } + } + + init { + this.logcatActivity = WeakReference(logcatActivity) + this.filterLevel = filterLevel + } + } + + companion object { + private const val LOGCAT_PATH = "logcat.log" + private const val MAX_BUFFER_LOG = 10000 + private const val SHARE_ID = 0 + private const val LEVEL_ID = 1 + private val log = PlatformUtil.getLog(TrackerLogcatActivity::class.java) + } +} diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index e3071e5fbf..8bfbd5b688 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -13,10 +13,13 @@ libs/it.unibo.alice.tuprolog-tuprolog-3.2.1.jar libs/commons-codec-commons-codec-1.11.jar libs/OsmAndCore_android-0.1-SNAPSHOT.jar +# Huawei libs/huawei-*.jar huaweidrmlib/ HwDRM_SDK_* drm_strings.xml +agconnect-services.json +OsmAndHms.jks # copy_widget_icons.sh res/drawable-large/map_* diff --git a/OsmAnd/AndroidManifest-freecustom.xml b/OsmAnd/AndroidManifest-freecustom.xml deleted file mode 100644 index 66ced1602a..0000000000 --- a/OsmAnd/AndroidManifest-freecustom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/OsmAnd/AndroidManifest-freehuawei.xml b/OsmAnd/AndroidManifest-freehuawei.xml index e96bb7fe47..c557d5fe2a 100644 --- a/OsmAnd/AndroidManifest-freehuawei.xml +++ b/OsmAnd/AndroidManifest-freehuawei.xml @@ -2,24 +2,31 @@ - - - - + + + + + + + + - + tools:replace="android:authorities" + android:authorities="net.osmand.huawei.fileprovider"/> - \ No newline at end of file diff --git a/OsmAnd/AndroidManifest-huawei.xml b/OsmAnd/AndroidManifest-huawei.xml deleted file mode 100644 index bc847980cd..0000000000 --- a/OsmAnd/AndroidManifest-huawei.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index f2d870080b..85235f8647 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -7,7 +7,7 @@ - + @@ -52,8 +52,9 @@ + android:theme="@style/OsmandDarkTheme" android:restoreAnyVersion="true" android:largeHeap="true" + android:supportsRtl="true" android:usesCleartextTraffic="true" + android:hasFragileUserData="true" android:requestLegacyExternalStorage="true"> @@ -65,7 +66,8 @@ - + + + @@ -261,6 +264,7 @@ + @@ -371,6 +375,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -399,6 +469,13 @@ + + + + + + + @@ -408,22 +485,10 @@ - - - - - - - - - - - - @@ -484,11 +549,17 @@ - + + + + + + + @@ -953,7 +1024,7 @@ + diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index e33b1ca75f..42642fc2cb 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -44,11 +44,11 @@ android { defaultConfig { minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 - targetSdkVersion 28 - versionCode 370 + targetSdkVersion 29 + versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode multiDexEnabled true - versionName "3.7.0" + versionName "3.9.0" versionName System.getenv("APK_VERSION")? System.getenv("APK_VERSION").toString(): versionName versionName System.getenv("APK_VERSION_SUFFIX")? versionName + System.getenv("APK_VERSION_SUFFIX").toString(): versionName // Stops the Gradle plugin’s automatic rasterization of vectors @@ -107,19 +107,19 @@ android { debug { manifest.srcFile "AndroidManifest-debug.xml" } + full { + java.srcDirs = ["src-google"] + } free { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-free.xml" } freedev { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-freedev.xml" } - freecustom { - manifest.srcFile "AndroidManifest-freecustom.xml" - } - huawei { - manifest.srcFile "AndroidManifest-huawei.xml" - } freehuawei { + java.srcDirs = ["src-huawei"] manifest.srcFile "AndroidManifest-freehuawei.xml" } @@ -172,43 +172,24 @@ android { dimension "version" applicationId "net.osmand" } - freeres { - dimension "version" - applicationId "net.osmand" - resConfig "en" - } - freecustom { - dimension "version" - applicationId "net.osmand.freecustom" - } full { dimension "version" applicationId "net.osmand.plus" } - fulldev { - dimension "version" - applicationId "net.osmand.plus" - resConfig "en" - //resConfigs "xxhdpi", "nodpi" - } - huawei { - dimension "version" - applicationId "net.osmand.plus.huawei" - } freehuawei { dimension "version" applicationId "net.osmand.huawei" } - // CoreVersion + // Build that doesn't include 3D OpenGL legacy { dimension "coreversion" } - + // Build that includes 3D OpenGL release qtcore { dimension "coreversion" } - + // Build that includes 3D OpenGL debug qtcoredebug { dimension "coreversion" } @@ -216,9 +197,11 @@ android { buildTypes { debug { + buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" signingConfig signingConfigs.development } release { + buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" signingConfig signingConfigs.publishing } } @@ -276,46 +259,6 @@ task downloadWorldMiniBasemap { } } -task downloadHuaweiDrmZip { - doLast { - ant.get(src: 'https://obs.cn-north-2.myhwclouds.com/hms-ds-wf/sdk/HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'HwDRM_SDK_2.5.2.300_ADT.zip', skipexisting: 'true') - ant.unzip(src: 'HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'huaweidrmlib/') - } -} - -task copyHuaweiDrmLibs(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/libs" - into "libs" -} - -task copyHuaweiDrmValues(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/res" - into "res" -} - -task downloadPrebuiltHuaweiDrm { - dependsOn copyHuaweiDrmLibs, copyHuaweiDrmValues -} - -task cleanHuaweiDrmLibs(type: Delete) { - delete "huaweidrmlib" - delete fileTree("libs").matching { - include "**/huawei-*.jar" - } -} - -task cleanHuaweiDrmValues(type: Delete) { - delete fileTree("res").matching { - include "**/drm_strings.xml" - } -} - -task cleanPrebuiltHuaweiDrm { - dependsOn cleanHuaweiDrmLibs, cleanHuaweiDrmValues -} - task collectVoiceAssets(type: Sync) { from "../../resources/voice" into "assets/voice" @@ -352,6 +295,9 @@ task collectHelpContentsAssets(type: Copy) { from("../../help/website/feature_articles") { include "*.html" } + from("../../help/website/blog_articles") { + include "osmand-3-8-released.html" + } into "assets/feature_articles" } @@ -397,8 +343,6 @@ task copyLargePOIIcons(type: Sync) { } } - - task copyWidgetIconsXhdpi(type: Sync) { from "res/drawable-xxhdpi/" into "res/drawable-large-xhdpi/" @@ -444,15 +388,6 @@ task collectExternalResources { copyWidgetIconsXhdpi, copyPoiCategories, downloadWorldMiniBasemap - - Gradle gradle = getGradle() - String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() - // Use Drm SDK only for huawei build - if (tskReqStr.contains("huawei")) { - dependsOn downloadPrebuiltHuaweiDrm - } else { - dependsOn cleanPrebuiltHuaweiDrm - } } // Legacy core build @@ -503,10 +438,16 @@ task cleanupDuplicatesInCore() { file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so")) } } + afterEvaluate { android.applicationVariants.all { variant -> variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) } + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() + if (tskReqStr.contains("huawei")) { + apply plugin: 'com.huawei.agconnect' + } } task appStart(type: Exec) { @@ -516,15 +457,14 @@ task appStart(type: Exec) { // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' } - dependencies { implementation project(path: ':OsmAnd-java', configuration: 'android') implementation project(':OsmAnd-api') implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'com.google.android.material:material:1.2.0-beta01' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.preference:preference:1.1.0' implementation fileTree(include: ['gnu-trove-osmand.jar', 'icu4j-49_1_patched.jar'], dir: 'libs') @@ -563,8 +503,10 @@ dependencies { implementation ("com.github.HITGIF:TextFieldBoxes:1.4.5"){ exclude group: 'com.android.support' } + implementation('com.github.scribejava:scribejava-apis:7.1.1'){ + exclude group: "com.fasterxml.jackson.core" + } implementation 'com.jaredrummler:colorpicker:1.1.0' - huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') - freehuaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') + freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' } diff --git a/OsmAnd/build.gradle.lib b/OsmAnd/build.gradle.lib new file mode 100644 index 0000000000..b70a881218 --- /dev/null +++ b/OsmAnd/build.gradle.lib @@ -0,0 +1,521 @@ +//apply plugin: 'com.android.application' +apply plugin: 'com.android.library' + +// Global Parameters accepted +// TARGET_APP_NAME - app name +// APK_NUMBER_VERSION - version number of apk +// APK_VERSION_SUFFIX - build number like #99999Z, appended (for dev builds) to Manifest's versionName as X.X.X#99999Z +// Z means flavor: M=-master, D=-main-default, B=-Blackberry, Des=-design, MQA=-main-qt-arm, MQDA=-main-qt-default-arm, S=-sherpafy +// APP_EDITION - date stamp of builds +// APP_FEATURES - features +play_market +gps_status -parking_plugin -blackberry -free_version -amazon + +// 1. To be done Filter fonts +// +// +// +// +// +// +// Less important + +task printc { + configurations.each { if(it.isCanBeResolved()) println it.name } +} + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + // compileNdkVersion "android-ndk-r17b" + + signingConfigs { + development { + storeFile file("../keystores/debug.keystore") + storePassword "android" + keyAlias "androiddebugkey" + keyPassword "android" + } + + publishing { + storeFile file("/var/lib/jenkins/osmand_key") + storePassword System.getenv("OSMAND_APK_PASSWORD") + keyAlias "osmand" + keyPassword System.getenv("OSMAND_APK_PASSWORD") + } + } + + defaultConfig { + minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 + targetSdkVersion 29 + versionCode 390 + versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode + multiDexEnabled true + versionName "3.9.0" + versionName System.getenv("APK_VERSION")? System.getenv("APK_VERSION").toString(): versionName + versionName System.getenv("APK_VERSION_SUFFIX")? versionName + System.getenv("APK_VERSION_SUFFIX").toString(): versionName + // Stops the Gradle plugin’s automatic rasterization of vectors + // vectorDrawables.generatedDensities = ['hdpi'] + vectorDrawables.useSupportLibrary = true + } + + lintOptions { + lintConfig file("lint.xml") + abortOnError false + warningsAsErrors false + } + + /* + bundle { + language { + // Specifies that the app bundle should not support + // configuration APKs for language resources. These + // resources are instead packaged with each base and + // dynamic feature APK. + enableSplit = false + } + } + */ + // related to kuromoji + //packagingOptions { + // exclude '/META-INF/CONTRIBUTORS.md' + // exclude '/META-INF/LICENSE.md' + // exclude '/META-INF/NOTICE.md' + //} + + // This is from OsmAndCore_android.aar - for some reason it's not inherited + aaptOptions { + // Don't compress any embedded resources + noCompress "qz" + cruncherEnabled = false + // Flag notifies aapt to keep the attribute IDs around + // additionalParameters "--no-version-vectors" + } + + dexOptions { + javaMaxHeapSize "4g" + } + + sourceSets { + main { + manifest.srcFile "AndroidManifest.xml" + jni.srcDirs = [] + jniLibs.srcDirs = ["libs"] + aidl.srcDirs = ["src"] + java.srcDirs = ["src", "src-google"] + resources.srcDirs = ["src"] + renderscript.srcDirs = ["src"] + res.srcDirs = ["res"] + assets.srcDirs = ["assets"] + } + debug { + manifest.srcFile "AndroidManifest-debug.xml" + } + /* + full { + java.srcDirs = ["src-google"] + } + free { + java.srcDirs = ["src-google"] + manifest.srcFile "AndroidManifest-free.xml" + } + freedev { + java.srcDirs = ["src-google"] + manifest.srcFile "AndroidManifest-freedev.xml" + } + freehuawei { + java.srcDirs = ["src-huawei"] + manifest.srcFile "AndroidManifest-freehuawei.xml" + } + */ + legacy { + jniLibs.srcDirs = ["libc++"] + } + } + + flavorDimensions "coreversion", "abi" + productFlavors { + // ABI + armv7 { + dimension "abi" + ndk { + abiFilter 'armeabi-v7a' + } + } + arm64 { + dimension "abi" + ndk { + abiFilter 'arm64-v8a' + } + } + x86 { + dimension "abi" + ndk { + abiFilters 'x86', 'x86_64' + } + } + armonly { + dimension "abi" + ndk { + abiFilters 'arm64-v8a', 'armeabi-v7a' + } + } + fat { + dimension "abi" + ndk { + abiFilters 'arm64-v8a', 'x86', 'x86_64', 'armeabi-v7a' + } + } + + /* + // Version + freedev { + dimension "version" + applicationId "net.osmand.dev" + // resConfig "en" + } + free { + dimension "version" + applicationId "net.osmand" + } + full { + dimension "version" + applicationId "net.osmand.plus" + } + freehuawei { + dimension "version" + applicationId "net.osmand.huawei" + } + */ + // CoreVersion + // Build that doesn't include 3D OpenGL + legacy { + dimension "coreversion" + } + // Build that includes 3D OpenGL release + qtcore { + dimension "coreversion" + } + // Build that includes 3D OpenGL debug + qtcoredebug { + dimension "coreversion" + } + } + + buildTypes { + debug { + buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" + buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\"" + buildConfigField "String", "OSM_OAUTH_CONSUMER_SECRET", "\"lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM\"" + signingConfig signingConfigs.development + } + release { + buildConfigField "String", "OPR_BASE_URL", "\"https://test.openplacereviews.org/\"" + buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\"" + buildConfigField "String", "OSM_OAUTH_CONSUMER_SECRET", "\"lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM\"" + signingConfig signingConfigs.publishing + } + } + +} + +def replaceNoTranslate(line) { + if (line.contains("\"app_name\"") && System.getenv("TARGET_APP_NAME")) { + return line.replaceAll(">[^<]*<", ">" + System.getenv("TARGET_APP_NAME") + "<") + } + if (line.contains("\"app_name_free\"") && System.getenv("TARGET_APP_NAME")) { + return line.replaceAll(">[^<]*<", ">" + System.getenv("TARGET_APP_NAME") + "<") + } + if (line.contains("\"app_edition\"") && System.getenv("APP_EDITION")) { + return line.replaceAll(">[^<]*<", ">" + System.getenv("APP_EDITION") + "<") + } + if (line.contains("\"versionFeatures\"") && System.getenv("APP_FEATURES")) { + return line.replaceAll(">[^<]*<", ">" + System.getenv("APP_FEATURES") + "<") + } + return line; +} + +task updateNoTranslate(type: Copy) { + from('.') { + include 'no_translate.xml' + filter { + line -> replaceNoTranslate(line); + } + } + into 'res/values/' +} + +task validateTranslate { + println "Validating translations" + + file("res").eachFileRecurse groovy.io.FileType.FILES, { + if (it.name == "strings.xml" || it.name == "phrases.xml") { + it.eachLine { line -> + if (line.contains("\$ s") || line.contains("\$ d") || line.contains("\$ f") || + line.contains(" \$s") || line.contains(" \$d") || line.contains(" \$f") || + line.contains("1\$ ") || line.contains("2\$ ") || line.contains("3\$ ") || + line.contains("%1s") || line.contains(" 1\$s") || + (line.contains("% \$") || line.contains("% 1") || line.contains("% 2") || + line.contains("% 3") || line.contains("% s"))) { + throw new GradleException("Incorrect translation " + it.getAbsolutePath() + " " + line); + } + } + } + } +} + +task downloadWorldMiniBasemap { + doLast { + ant.get(src: 'http://builder.osmand.net/basemap/World_basemap_mini_2.obf', dest: 'assets/World_basemap_mini.obf', skipexisting: 'true') + } +} + +task collectVoiceAssets(type: Sync) { + from "../../resources/voice" + into "assets/voice" + include "**/*.js" +} + +task cleanNoTranslate(type: Delete) { + delete('res/values/no_translate.xml') +} + +task collectFonts(type: Copy) { + from "../../resources/fonts" + from "../../resources/rendering_styles/fonts" +// from "../../resources/rendering_styles/fonts/OpenSans" + into "assets/fonts" + include "*.ttf" +} + +task collectHelpContentsStyle(type: Copy) { + from("../../help/website/help/") { + include "style.css" + } + into "assets" +} + +task collectHelpContentsAssets(type: Copy) { + from("../../help/website/help") { + include "about.html" + include "changes.html" + include "faq.html" + include "technical-articles.html" + include "map-legend.html" + } + from("../../help/website/feature_articles") { + include "*.html" + } + from("../../help/website/blog_articles") { + include "osmand-3-8-released.html" + } + into "assets/feature_articles" +} + +task copyPoiCategories(type: Copy) { + from("../../resources/poi") { + include "poi_categories.json" + } + into "assets" +} + +task copyMapShaderIcons(type: Sync) { + // from "../../resources/rendering_styles/style-icons/map-shaders-png" + // into "res/" + from "../../resources/rendering_styles/style-icons/map-shaders-vector" + into "res/drawable" + include "**/*.png", "**/*.xml" + preserve { + include '**/*' + exclude "**/h_*" + } +} + +task copyMapPOIIcons(type: Sync) { + from "../../resources/rendering_styles/style-icons/map-icons-vector" + into "res/drawable/" + // from "../../resources/rendering_styles/style-icons/map-icons-png" + // into "res/" + + include "**/*.png", "**/*.xml" + preserve { + include '**/*' + exclude "**/mm_*" + } +} + +task copyLargePOIIcons(type: Sync) { + from "../../resources/rendering_styles/style-icons/poi-icons-vector" + into "res/drawable/" + include "**/*.png", "**/*.xml" + preserve { + include '**/*' + exclude "**/mx_*" + } +} + +task copyWidgetIconsXhdpi(type: Sync) { + from "res/drawable-xxhdpi/" + into "res/drawable-large-xhdpi/" + include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + preserve { + include '*' + exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + } +} + +task copyWidgetIconsHdpi(type: Sync) { + from "res/drawable-xhdpi/" + into "res/drawable-large-hdpi/" + include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + preserve { + include '*' + exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + } +} + +task copyWidgetIcons(type: Sync) { + from "res/drawable-hdpi/" + into "res/drawable-large/" + include "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + preserve { + include '*' + exclude "**/widget_*.png", "**/widget_*.xml", "**/map_*.xml", "**/map_*.png" + } +} + +task collectExternalResources { + dependsOn collectVoiceAssets, + collectFonts, + collectHelpContentsAssets, + collectHelpContentsStyle, + copyMapShaderIcons, + copyMapPOIIcons, + copyLargePOIIcons, + updateNoTranslate, + validateTranslate, + copyWidgetIcons, + copyWidgetIconsHdpi, + copyWidgetIconsXhdpi, + copyPoiCategories, + downloadWorldMiniBasemap +} + +// Legacy core build +import org.apache.tools.ant.taskdefs.condition.Os + +task buildOsmAndCore(type: Exec) { + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() + String flavour = ""; + if(!tskReqStr.contains("fat")) { + if(tskReqStr.contains("arm64")) { + flavour = flavour.length() == 0 ? "ARM64_ONLY" : "" + } + if(tskReqStr.contains("armv7")) { + flavour = flavour.length() == 0 ? "ARMV7_ONLY" : "" + } + if(tskReqStr.contains("armonly")) { + flavour = flavour.length() == 0 ? "ARM_ONLY" : "" + } + if(tskReqStr.contains("x86")) { + flavour = flavour.length() == 0 ? "X86_ONLY" : "" + } + } + + description "Build Legacy OsmAndCore" + + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + if(flavour.length() > 0) { + environment "$flavour", "1" + } + commandLine "bash", file("./old-ndk-build.sh").getAbsolutePath() + } else { + commandLine "cmd", "/c", "echo", "Not supported" + } +} + +task cleanupDuplicatesInCore() { + dependsOn buildOsmAndCore + // doesn't work for legacy debug builds + doLast { + file("libc++/armeabi-v7a").mkdirs() + file("libs/armeabi-v7a/libc++_shared.so").renameTo(file("libc++/armeabi-v7a/libc++_shared.so")) + file("libc++/arm64-v8a").mkdirs() + file("libs/arm64-v8a/libc++_shared.so").renameTo(file("libc++/arm64-v8a/libc++_shared.so")) + file("libc++/x86").mkdirs() + file("libs/x86/libc++_shared.so").renameTo(file("libc++/x86/libc++_shared.so")) + file("libc++/x86_64").mkdirs() + file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so")) + } +} + +afterEvaluate { + android.libraryVariants.all { variant -> + variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) + } + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() + if (tskReqStr.contains("huawei")) { + apply plugin: 'com.huawei.agconnect' + } +} + +task appStart(type: Exec) { + // linux + commandLine 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' + // windows + // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' +} + +dependencies { + implementation project(path: ':OsmAnd-java', configuration: 'android') + implementation project(':OsmAnd-api') + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.browser:browser:1.0.0' + implementation 'androidx.preference:preference:1.1.0' + implementation fileTree(include: ['gnu-trove-osmand.jar', 'icu4j-49_1_patched.jar'], dir: 'libs') + + implementation group: 'commons-logging', name: 'commons-logging', version: '1.2' + implementation 'commons-codec:commons-codec:1.11' + implementation 'it.unibo.alice.tuprolog:tuprolog:3.2.1' + implementation 'org.apache.commons:commons-compress:1.17' + implementation 'com.moparisthebest:junidecode:0.1.1' + implementation 'org.immutables:gson:2.5.0' + implementation 'com.vividsolutions:jts-core:1.14.0' + implementation 'com.google.openlocationcode:openlocationcode:1.0.4' + implementation 'com.android.billingclient:billing:2.0.3' + // turn off for now + //implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0' + implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' + // JS core + implementation group: 'org.mozilla', name: 'rhino', version: '1.7.9' +// size restrictions +// implementation 'com.ibm.icu:icu4j:50.1' +// implementation 'net.sf.trove4j:trove4j:3.0.3' + + qtcoreImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') + qtcoredebugImplementation fileTree(include: ['QtAndroid.jar', 'QtAndroidBearer.jar'], dir: 'libs') + + legacyImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@jar" + qtcoredebugImplementation "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar" + qtcoredebugImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" + qtcoreImplementation "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar" + qtcoreImplementation "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar" + implementation ("com.getkeepsafe.taptargetview:taptargetview:1.12.0"){ + exclude group: 'com.android.support' + } + implementation 'com.github.PhilJay:MPAndroidChart:v3.0.1' + implementation ("com.github.HITGIF:TextFieldBoxes:1.4.5"){ + exclude group: 'com.android.support' + } + implementation('com.github.scribejava:scribejava-apis:7.1.1'){ + exclude group: "com.fasterxml.jackson.core" + } + implementation 'com.jaredrummler:colorpicker:1.1.0' + + //freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' +} diff --git a/OsmAnd/res/color/background_field.xml b/OsmAnd/res/color/background_field.xml new file mode 100644 index 0000000000..39536d5537 --- /dev/null +++ b/OsmAnd/res/color/background_field.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OsmAnd/res/color/hint_label.xml b/OsmAnd/res/color/hint_label.xml new file mode 100644 index 0000000000..31721d15b6 --- /dev/null +++ b/OsmAnd/res/color/hint_label.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/bottom_sheet_buttons_bg_dark.xml b/OsmAnd/res/drawable/bottom_sheet_buttons_bg_dark.xml new file mode 100644 index 0000000000..1322edb6cb --- /dev/null +++ b/OsmAnd/res/drawable/bottom_sheet_buttons_bg_dark.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/bottom_sheet_buttons_bg_light.xml b/OsmAnd/res/drawable/bottom_sheet_buttons_bg_light.xml new file mode 100644 index 0000000000..cfc0b32c50 --- /dev/null +++ b/OsmAnd/res/drawable/bottom_sheet_buttons_bg_light.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/ic_action_device_location.xml b/OsmAnd/res/drawable/ic_action_device_location.xml new file mode 100644 index 0000000000..c804224052 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_device_location.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_device_location_colored.xml b/OsmAnd/res/drawable/ic_action_device_location_colored.xml new file mode 100644 index 0000000000..2bceb2d67e --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_device_location_colored.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_direction_arrow.xml b/OsmAnd/res/drawable/ic_action_direction_arrow.xml index 5e97fe8338..49bec48f22 100644 --- a/OsmAnd/res/drawable/ic_action_direction_arrow.xml +++ b/OsmAnd/res/drawable/ic_action_direction_arrow.xml @@ -1,9 +1,9 @@ + android:viewportHeight="12"> diff --git a/OsmAnd/res/drawable/ic_action_file_routing.xml b/OsmAnd/res/drawable/ic_action_file_routing.xml new file mode 100644 index 0000000000..9ec5774abe --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_file_routing.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_kayak.xml b/OsmAnd/res/drawable/ic_action_kayak.xml new file mode 100644 index 0000000000..7cf5704fea --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_kayak.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/OsmAnd/res/drawable/ic_action_logout.xml b/OsmAnd/res/drawable/ic_action_logout.xml new file mode 100644 index 0000000000..e1f0b60564 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_logout.xml @@ -0,0 +1,12 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_action_motorboat.xml b/OsmAnd/res/drawable/ic_action_motorboat.xml new file mode 100644 index 0000000000..a9b3e57b66 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_motorboat.xml @@ -0,0 +1,13 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_action_openstreetmap_logo_colored.xml b/OsmAnd/res/drawable/ic_action_openstreetmap_logo_colored.xml new file mode 100644 index 0000000000..2d2472c970 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_openstreetmap_logo_colored.xml @@ -0,0 +1,14 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_action_photo_upload.xml b/OsmAnd/res/drawable/ic_action_photo_upload.xml new file mode 100644 index 0000000000..30d7e17280 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_photo_upload.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_photo_upload_colored.xml b/OsmAnd/res/drawable/ic_action_photo_upload_colored.xml new file mode 100644 index 0000000000..b932165336 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_photo_upload_colored.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_plan_route.xml b/OsmAnd/res/drawable/ic_action_plan_route.xml new file mode 100644 index 0000000000..ba438f1f16 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_plan_route.xml @@ -0,0 +1,43 @@ + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_plan_route_point_colored.xml b/OsmAnd/res/drawable/ic_action_plan_route_point_colored.xml new file mode 100644 index 0000000000..7fb6be0078 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_plan_route_point_colored.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/OsmAnd/res/drawable/ic_action_route_part.xml b/OsmAnd/res/drawable/ic_action_route_part.xml new file mode 100644 index 0000000000..e06263ef8e --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_route_part.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_route_start_destination.xml b/OsmAnd/res/drawable/ic_action_route_start_destination.xml new file mode 100644 index 0000000000..b658f465fc --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_route_start_destination.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_sdcard_warning.xml b/OsmAnd/res/drawable/ic_action_sdcard_warning.xml new file mode 100644 index 0000000000..2a36e408c5 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_sdcard_warning.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_sdcard_warning_colored.xml b/OsmAnd/res/drawable/ic_action_sdcard_warning_colored.xml new file mode 100644 index 0000000000..ab253f6e38 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_sdcard_warning_colored.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_sort_by_name_ascending.xml b/OsmAnd/res/drawable/ic_action_sort_by_name_ascending.xml new file mode 100644 index 0000000000..07406ae2c7 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_sort_by_name_ascending.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_sort_by_name_descending.xml b/OsmAnd/res/drawable/ic_action_sort_by_name_descending.xml new file mode 100644 index 0000000000..04cd095335 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_sort_by_name_descending.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_action_user_account.xml b/OsmAnd/res/drawable/ic_action_user_account.xml new file mode 100644 index 0000000000..51beae9b6e --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_user_account.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/OsmAnd/res/drawable/ic_img_logo_openplacereview.xml b/OsmAnd/res/drawable/ic_img_logo_openplacereview.xml new file mode 100644 index 0000000000..3ef860a90c --- /dev/null +++ b/OsmAnd/res/drawable/ic_img_logo_openplacereview.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_logo_openplacereview.xml b/OsmAnd/res/drawable/ic_logo_openplacereview.xml new file mode 100644 index 0000000000..fc5b26cbf3 --- /dev/null +++ b/OsmAnd/res/drawable/ic_logo_openplacereview.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/OsmAnd/res/drawable/ic_sample.xml b/OsmAnd/res/drawable/ic_sample.xml new file mode 100644 index 0000000000..9612304bcf --- /dev/null +++ b/OsmAnd/res/drawable/ic_sample.xml @@ -0,0 +1,13 @@ + + + + diff --git a/OsmAnd/res/drawable/ic_type_audio.xml b/OsmAnd/res/drawable/ic_type_audio.xml index 4b6b93dd87..96d1ba82e6 100644 --- a/OsmAnd/res/drawable/ic_type_audio.xml +++ b/OsmAnd/res/drawable/ic_type_audio.xml @@ -4,6 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24"> + android:pathData="M4,4C2.8954,4 2,4.8954 2,6V18C2,19.1046 2.8954,20 4,20H20C21.1046,20 22,19.1046 22,18V6C22,4.8954 21.1046,4 20,4H4ZM16,6H12V12.1707C11.6872,12.0602 11.3506,12 11,12C9.3432,12 8,13.3431 8,15C8,16.6569 9.3432,18 11,18C12.6569,18 14,16.6569 14,15V9H16V6Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> diff --git a/OsmAnd/res/drawable/ic_type_img.xml b/OsmAnd/res/drawable/ic_type_img.xml index 868835e18a..d583e48ee4 100644 --- a/OsmAnd/res/drawable/ic_type_img.xml +++ b/OsmAnd/res/drawable/ic_type_img.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/OsmAnd/res/drawable/ic_type_video.xml b/OsmAnd/res/drawable/ic_type_video.xml index 4d95b88629..5ae4a9a14a 100644 --- a/OsmAnd/res/drawable/ic_type_video.xml +++ b/OsmAnd/res/drawable/ic_type_video.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/OsmAnd/res/drawable/layout_bg.xml b/OsmAnd/res/drawable/layout_bg.xml new file mode 100644 index 0000000000..214e67e852 --- /dev/null +++ b/OsmAnd/res/drawable/layout_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/OsmAnd/res/drawable/layout_bg_dark.xml b/OsmAnd/res/drawable/layout_bg_dark.xml new file mode 100644 index 0000000000..f821ad3078 --- /dev/null +++ b/OsmAnd/res/drawable/layout_bg_dark.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/OsmAnd/res/drawable/layout_bg_dark_solid.xml b/OsmAnd/res/drawable/layout_bg_dark_solid.xml new file mode 100644 index 0000000000..59a1c38141 --- /dev/null +++ b/OsmAnd/res/drawable/layout_bg_dark_solid.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/layout_bg_solid.xml b/OsmAnd/res/drawable/layout_bg_solid.xml new file mode 100644 index 0000000000..0824ba1b7b --- /dev/null +++ b/OsmAnd/res/drawable/layout_bg_solid.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/shadow.xml b/OsmAnd/res/drawable/shadow.xml new file mode 100644 index 0000000000..25b8ac43b3 --- /dev/null +++ b/OsmAnd/res/drawable/shadow.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/account_details.xml b/OsmAnd/res/layout/account_details.xml new file mode 100644 index 0000000000..9e6e4497d7 --- /dev/null +++ b/OsmAnd/res/layout/account_details.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/activity_opr_webview.xml b/OsmAnd/res/layout/activity_opr_webview.xml new file mode 100644 index 0000000000..9d3f28c67a --- /dev/null +++ b/OsmAnd/res/layout/activity_opr_webview.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_buttons_vertical.xml b/OsmAnd/res/layout/bottom_buttons_vertical.xml new file mode 100644 index 0000000000..0de7d48389 --- /dev/null +++ b/OsmAnd/res/layout/bottom_buttons_vertical.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_behaviour_base.xml b/OsmAnd/res/layout/bottom_sheet_behaviour_base.xml new file mode 100644 index 0000000000..af79d3e289 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_behaviour_base.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_dialog_button.xml b/OsmAnd/res/layout/bottom_sheet_dialog_button.xml index ee757eba8f..6ce7d34fc3 100644 --- a/OsmAnd/res/layout/bottom_sheet_dialog_button.xml +++ b/OsmAnd/res/layout/bottom_sheet_dialog_button.xml @@ -7,7 +7,7 @@ android:layout_height="@dimen/dialog_button_height" android:layout_weight="1"> - - + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_item_slider_with_two_text.xml b/OsmAnd/res/layout/bottom_sheet_item_slider_with_two_text.xml index b5cb11cf49..f386e8c59f 100644 --- a/OsmAnd/res/layout/bottom_sheet_item_slider_with_two_text.xml +++ b/OsmAnd/res/layout/bottom_sheet_item_slider_with_two_text.xml @@ -6,33 +6,37 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + osmand:typeface="@string/font_roboto_regular" + tools:text="Some very long title to check overlapped texts" /> + + + osmand:typeface="@string/font_roboto_medium" + tools:text="summary" /> - + diff --git a/OsmAnd/res/layout/bottom_sheet_login.xml b/OsmAnd/res/layout/bottom_sheet_login.xml new file mode 100644 index 0000000000..85915a72cc --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_login.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_menu_base.xml b/OsmAnd/res/layout/bottom_sheet_menu_base.xml index 5110455a7c..cb4716cf00 100644 --- a/OsmAnd/res/layout/bottom_sheet_menu_base.xml +++ b/OsmAnd/res/layout/bottom_sheet_menu_base.xml @@ -33,12 +33,10 @@ + android:background="@drawable/shadow" + android:visibility="gone" /> - - - + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_plan_route_select_file.xml b/OsmAnd/res/layout/bottom_sheet_plan_route_select_file.xml index da5f1dff61..10de9cb667 100644 --- a/OsmAnd/res/layout/bottom_sheet_plan_route_select_file.xml +++ b/OsmAnd/res/layout/bottom_sheet_plan_route_select_file.xml @@ -1,5 +1,5 @@ - + android:orientation="horizontal"> - + android:layout_weight="1" + android:orientation="vertical"> - + - - - + + + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_plan_route_start.xml b/OsmAnd/res/layout/bottom_sheet_plan_route_start.xml index a4aa9f312e..d43c589a9d 100644 --- a/OsmAnd/res/layout/bottom_sheet_plan_route_start.xml +++ b/OsmAnd/res/layout/bottom_sheet_plan_route_start.xml @@ -1,47 +1,40 @@ - + android:orientation="horizontal" + android:paddingStart="@dimen/content_padding" + android:paddingLeft="@dimen/content_padding" + android:paddingTop="@dimen/bottom_sheet_title_padding_top" + android:paddingEnd="@dimen/content_padding" + android:paddingRight="@dimen/content_padding" + android:paddingBottom="@dimen/bottom_sheet_title_padding_bottom"> - - - - - - - + android:letterSpacing="@dimen/description_letter_spacing" + android:text="@string/plan_route_last_edited" + android:textColor="?attr/active_color_basic" + android:textSize="@dimen/default_desc_text_size" + osmand:typeface="@string/font_roboto_medium" /> - \ No newline at end of file + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/colors_card.xml b/OsmAnd/res/layout/colors_card.xml new file mode 100644 index 0000000000..bef11dc500 --- /dev/null +++ b/OsmAnd/res/layout/colors_card.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/custom_color_picker.xml b/OsmAnd/res/layout/custom_color_picker.xml index db6c3a254c..f2133e410b 100644 --- a/OsmAnd/res/layout/custom_color_picker.xml +++ b/OsmAnd/res/layout/custom_color_picker.xml @@ -46,9 +46,11 @@ android:id="@+id/color_hex_edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textMultiLine" - android:maxLines="4" + android:inputType="text" + android:maxLines="1" android:minHeight="@dimen/bottom_sheet_list_item_height" + android:paddingTop="@dimen/content_padding_small" + android:paddingBottom="@dimen/content_padding_small" android:paddingStart="@dimen/content_padding_small" android:paddingLeft="@dimen/content_padding_small" android:paddingEnd="@dimen/content_padding_small" diff --git a/OsmAnd/res/layout/custom_radio_buttons.xml b/OsmAnd/res/layout/custom_radio_buttons.xml index d768e58004..460356e875 100644 --- a/OsmAnd/res/layout/custom_radio_buttons.xml +++ b/OsmAnd/res/layout/custom_radio_buttons.xml @@ -26,27 +26,18 @@ android:background="?attr/selectableItemBackground" android:gravity="center" android:textSize="@dimen/default_desc_text_size" + android:textColor="@drawable/radio_flat_text_selector_light" osmand:typeface="@string/font_roboto_medium" tools:text="@string/shared_string_left"/> - - + android:layout_weight="0" + android:background="?attr/divider_color"> @@ -62,8 +53,9 @@ android:layout_height="match_parent" android:background="?attr/selectableItemBackground" android:gravity="center" - android:textSize="@dimen/default_desc_text_size" + android:textColor="@drawable/radio_flat_text_selector_light" osmand:typeface="@string/font_roboto_medium" + android:textSize="@dimen/default_desc_text_size" tools:text="@string/shared_string_right"/> diff --git a/OsmAnd/res/layout/edit_arrangement_list_fragment.xml b/OsmAnd/res/layout/edit_arrangement_list_fragment.xml index f0003875a2..2cc1e020cb 100644 --- a/OsmAnd/res/layout/edit_arrangement_list_fragment.xml +++ b/OsmAnd/res/layout/edit_arrangement_list_fragment.xml @@ -32,8 +32,8 @@ + android:layout_height="8dp" + android:background="@drawable/shadow" /> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/follow_track_options.xml b/OsmAnd/res/layout/follow_track_options.xml index 69f0c467c3..0877fcc307 100644 --- a/OsmAnd/res/layout/follow_track_options.xml +++ b/OsmAnd/res/layout/follow_track_options.xml @@ -18,7 +18,7 @@ android:id="@+id/route_menu_top_shadow_all" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/card_and_list_background_basic" + android:background="?attr/bg_color" android:minHeight="@dimen/bottom_sheet_title_height" android:orientation="vertical"> @@ -55,6 +55,17 @@ android:textSize="@dimen/default_list_text_size" osmand:typeface="@string/font_roboto_medium" /> + + @@ -128,9 +139,9 @@ + android:background="@drawable/shadow" /> + android:background="@drawable/shadow" /> + android:layout_height="wrap_content" + android:orientation="horizontal"> - + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingLeft="@dimen/content_padding" + android:paddingTop="@dimen/bottom_sheet_title_padding_bottom" + android:paddingRight="@dimen/content_padding" + android:paddingBottom="@dimen/bottom_sheet_title_padding_bottom" + android:visibility="invisible" + tools:visibility="visible"> + + + osmand:lineHeight="@dimen/default_desc_line_height" + osmand:typeface="@string/font_roboto_regular" /> - + - + - + layout="@layout/bottom_sheet_dialog_button" + android:visibility="visible" /> - + @@ -95,8 +102,8 @@ + osmand:title="@string/shared_string_import" + osmand:titleMarginEnd="0dp" + osmand:titleMarginStart="0dp"> @@ -132,8 +139,8 @@ style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="5dp" - android:visibility="gone" android:indeterminate="true" + android:visibility="gone" osmand:mpb_progressStyle="horizontal" osmand:mpb_setBothDrawables="true" osmand:mpb_useIntrinsicPadding="false" diff --git a/OsmAnd/res/layout/fragment_measurement_tool.xml b/OsmAnd/res/layout/fragment_measurement_tool.xml index fe1ba9b1d7..82cd085ec9 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool.xml @@ -30,22 +30,23 @@ + tools:src="@drawable/ic_action_ruler" /> + + + + + + + + + + + - - - - - - - - - + android:background="?attr/dashboard_divider" /> + android:minWidth="@dimen/measurement_tool_button_width" /> + diff --git a/OsmAnd/res/layout/fragment_measurement_tool_graph.xml b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml new file mode 100644 index 0000000000..555c1b6928 --- /dev/null +++ b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml b/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml new file mode 100644 index 0000000000..a0684707ea --- /dev/null +++ b/OsmAnd/res/layout/fragment_measurement_tool_points_list.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_opr_login.xml b/OsmAnd/res/layout/fragment_opr_login.xml new file mode 100644 index 0000000000..5c5d4c23b5 --- /dev/null +++ b/OsmAnd/res/layout/fragment_opr_login.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_plan_route_warning.xml b/OsmAnd/res/layout/fragment_plan_route_warning.xml new file mode 100644 index 0000000000..3b7019dd9e --- /dev/null +++ b/OsmAnd/res/layout/fragment_plan_route_warning.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_route_between_points_bottom_sheet_dialog.xml b/OsmAnd/res/layout/fragment_route_between_points_bottom_sheet_dialog.xml index 99b4cf41ff..49a09730d6 100644 --- a/OsmAnd/res/layout/fragment_route_between_points_bottom_sheet_dialog.xml +++ b/OsmAnd/res/layout/fragment_route_between_points_bottom_sheet_dialog.xml @@ -1,36 +1,32 @@ - + android:layout_height="wrap_content" + android:background="?attr/bg_color" + android:orientation="vertical"> + android:orientation="vertical" + android:paddingBottom="@dimen/bottom_sheet_content_padding_small"> - - - + android:gravity="center_vertical" + android:paddingStart="@dimen/content_padding" + android:paddingLeft="@dimen/content_padding" + android:paddingTop="@dimen/measurement_tool_menu_title_padding_top" + android:paddingEnd="@dimen/content_padding" + android:paddingRight="@dimen/content_padding" + android:paddingBottom="@dimen/measurement_tool_menu_title_padding_bottom" + android:text="@string/route_between_points" + android:textAppearance="@style/TextAppearance.ListItemTitle" + osmand:typeface="@string/font_roboto_medium" /> - + - - - \ No newline at end of file + \ No newline at end of file diff --git a/OsmAnd/res/layout/help_to_improve_item.xml b/OsmAnd/res/layout/help_to_improve_item.xml index 795053dbbe..71f94da43d 100644 --- a/OsmAnd/res/layout/help_to_improve_item.xml +++ b/OsmAnd/res/layout/help_to_improve_item.xml @@ -1,51 +1,54 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="16dp" + android:paddingBottom="16dp">