diff --git a/OsmAnd-java/src/main/java/net/osmand/map/ITileSource.java b/OsmAnd-java/src/main/java/net/osmand/map/ITileSource.java index b33c232c29..503467b93b 100644 --- a/OsmAnd-java/src/main/java/net/osmand/map/ITileSource.java +++ b/OsmAnd-java/src/main/java/net/osmand/map/ITileSource.java @@ -12,6 +12,8 @@ public interface ITileSource { public String getUrlToLoad(int x, int y, int zoom); + public String getUrlTemplate(); + public byte[] getBytes(int x, int y, int zoom, String dirWithTiles) throws IOException; public int getMinimumZoomSupported(); @@ -32,4 +34,15 @@ public interface ITileSource { public void deleteTiles(String path); + public int getAvgSize(); + + public String getRule(); + + public String getRandoms(); + + public boolean isInvertedYTile(); + + public boolean isTimeSupported(); + + public boolean getInversiveZoom(); } diff --git a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java index 6e52928a68..41f0097f6e 100644 --- a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java +++ b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java @@ -174,6 +174,16 @@ public class TileSourceManager { return invertedYTile; } + @Override + public boolean isTimeSupported() { + return expirationTimeMillis != -1; + } + + @Override + public boolean getInversiveZoom() { + return false; + } + public void setInvertedYTile(boolean invertedYTile) { this.invertedYTile = invertedYTile; } @@ -410,6 +420,11 @@ public class TileSourceManager { } } } + + @Override + public int getAvgSize() { + return this.avgSize; + } } private static Map readMetaInfoFile(File dir) { diff --git a/OsmAnd/res/layout/bottom_sheet_item_additional_data.xml b/OsmAnd/res/layout/bottom_sheet_item_additional_data.xml new file mode 100644 index 0000000000..8a677926b4 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_additional_data.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/profile_data_list_item_child.xml b/OsmAnd/res/layout/profile_data_list_item_child.xml new file mode 100644 index 0000000000..be6477bb17 --- /dev/null +++ b/OsmAnd/res/layout/profile_data_list_item_child.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/profile_data_list_item_group.xml b/OsmAnd/res/layout/profile_data_list_item_group.xml new file mode 100644 index 0000000000..2f99266f6a --- /dev/null +++ b/OsmAnd/res/layout/profile_data_list_item_group.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index c50eae807c..4c13b072ff 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -50,6 +50,11 @@ %1$s/%2$s Sunset at %1$s Sunrise at %1$s + Routing + Custom rendering style + Include additional data + The imported profile contains additional data. Click Import to import only profile data or select additional data to import. + You can select additional data to export along with the profile. Permission is required to use this option. Check and share detailed logs of the application No routing rules in \'%1$s\'. Please choose another file. diff --git a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java index 0fd47b6452..d34ddcc75e 100644 --- a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java +++ b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java @@ -1,5 +1,6 @@ package net.osmand.plus; +import android.database.SQLException; import android.database.sqlite.SQLiteDiskIOException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -22,11 +23,31 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; +import static net.osmand.IndexConstants.SQLITE_EXT; +import static net.osmand.IndexConstants.TILES_INDEX_DIR; + public class SQLiteTileSource implements ITileSource { public static final String EXT = IndexConstants.SQLITE_EXT; private static final Log LOG = PlatformUtil.getLog(SQLiteTileSource.class); + + private static final String MIN_ZOOM = "minzoom"; + private static final String MAX_ZOOM = "maxzoom"; + private static final String URL = "url"; + private static final String RANDOMS = "randoms"; + private static final String ELLIPSOID = "ellipsoid"; + private static final String INVERTED_Y = "inverted_y"; + private static final String REFERER = "referer"; + private static final String TIME_SUPPORTED = "timesupported"; + private static final String EXPIRE_MINUTES = "expireminutes"; + + private static final String TILES_TABLE_CREATE = "CREATE TABLE IF NOT EXISTS tiles (x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL, s INTEGER, image BLOB, time INTEGER, PRIMARY KEY (x, y, z))"; + private static final String CREATE_INDEX_X = "CREATE INDEX index_tiles_on_x ON tiles (x)"; + private static final String CREATE_INDEX_Y = "CREATE INDEX index_tiles_on_y ON tiles (y)"; + private static final String CREATE_INDEX_Z = "CREATE INDEX index_tiles_on_z ON tiles (z)"; + private static final String CREATE_INDEX_S = "CREATE INDEX index_tiles_on_s ON tiles (s)"; + private static final String MAXZOOM_FIELD = "maxzoom"; private static final String MINZOOM_FIELD = "minzoom"; private static final String ELLIPSOID_FIELD = "ellipsoid"; @@ -37,7 +58,7 @@ public class SQLiteTileSource implements ITileSource { private String urlTemplate = null; private String name; private SQLiteConnection db = null; - private final File file; + private File file = null; private int minZoom = 1; private int maxZoom = 17; private boolean inversiveZoom = true; // BigPlanet @@ -76,7 +97,50 @@ public class SQLiteTileSource implements ITileSource { } } - + + public SQLiteTileSource(OsmandApplication ctx, String name, int minZoom, int maxZoom, String urlTemplate, + String randoms, boolean isEllipsoid, boolean invertedY, String referer, + boolean timeSupported, long expirationTimeMillis, boolean inversiveZoom) { + this.ctx = ctx; + this.name = name; + this.urlTemplate = urlTemplate; + this.maxZoom = maxZoom; + this.minZoom = minZoom; + this.isEllipsoid = isEllipsoid; + this.expirationTimeMillis = expirationTimeMillis; + this.randoms = randoms; + this.referer = referer; + this.invertedY = invertedY; + this.timeSupported = timeSupported; + this.inversiveZoom = inversiveZoom; + } + + public void createDataBase() { + db = ctx.getSQLiteAPI().getOrCreateDatabase( + ctx.getAppPath(TILES_INDEX_DIR).getAbsolutePath() + "/" + name + SQLITE_EXT, true); + + db.execSQL("CREATE TABLE IF NOT EXISTS info (" + + MIN_ZOOM + ", " + + MAX_ZOOM + + ");"); + db.execSQL("INSERT INTO info (" + MIN_ZOOM + "," + MAX_ZOOM + ") VALUES ('" + minZoom + "','" + maxZoom + "');"); + + addInfoColumn(URL, urlTemplate); + addInfoColumn(RANDOMS, randoms); + addInfoColumn(ELLIPSOID, isEllipsoid ? "1" : "0"); + addInfoColumn(INVERTED_Y, invertedY ? "1" : "0"); + addInfoColumn(REFERER, referer); + addInfoColumn(TIME_SUPPORTED, timeSupported ? "yes" : "no"); + addInfoColumn(EXPIRE_MINUTES, String.valueOf(getExpirationTimeMinutes())); + + db.execSQL(TILES_TABLE_CREATE); + db.execSQL(CREATE_INDEX_X); + db.execSQL(CREATE_INDEX_Y); + db.execSQL(CREATE_INDEX_Z); + db.execSQL(CREATE_INDEX_S); + db.close(); + } + @Override public int getBitDensity() { return base != null ? base.getBitDensity() : 16; @@ -121,6 +185,20 @@ public class SQLiteTileSource implements ITileSource { return TileSourceTemplate.buildUrlToLoad(urlTemplate, randomsArray, x, y, zoom); } + @Override + public String getUrlTemplate() { + if (this.urlTemplate != null) { + return this.urlTemplate; + } else { + SQLiteConnection db = getDatabase(); + if (db == null || urlTemplate == null) { + return null; + } else { + return this.urlTemplate; + } + } + } + @Override public int hashCode() { final int prime = 31; @@ -282,9 +360,13 @@ public class SQLiteTileSource implements ITileSource { } private void addInfoColumn(String columnName, String value) { - if (!onlyReadonlyAvailable) { - db.execSQL("alter table info add column " + columnName + " TEXT"); - db.execSQL("update info set " + columnName + " = '" + value + "'"); + if(!onlyReadonlyAvailable) { + try { + db.execSQL("alter table info add column " + columnName + " TEXT"); + } catch (SQLException e) { + LOG.info("Error adding column " + e); + } + db.execSQL("update info set "+columnName+" = '"+value+"'"); } } @@ -459,7 +541,37 @@ public class SQLiteTileSource implements ITileSource { } db.execSQL("DELETE FROM tiles"); } - + + @Override + public int getAvgSize() { + return base != null ? base.getAvgSize() : -1; + } + + @Override + public String getRule() { + return rule; + } + + @Override + public String getRandoms() { + return randoms; + } + + @Override + public boolean isInvertedYTile() { + return invertedY; + } + + @Override + public boolean isTimeSupported() { + return timeSupported; + } + + @Override + public boolean getInversiveZoom() { + return inversiveZoom; + } + /** * Makes method synchronized to give a little more time for get methods and * let all writing attempts to wait outside of this method @@ -551,5 +663,3 @@ public class SQLiteTileSource implements ITileSource { return urlTemplate; } } - - diff --git a/OsmAnd/src/net/osmand/plus/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/SettingsHelper.java index ddbcd94dea..b302cfd0e1 100644 --- a/OsmAnd/src/net/osmand/plus/SettingsHelper.java +++ b/OsmAnd/src/net/osmand/plus/SettingsHelper.java @@ -9,10 +9,21 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + import net.osmand.PlatformUtil; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.osm.MapPoiTypes; +import net.osmand.osm.PoiCategory; import net.osmand.plus.ApplicationMode.ApplicationModeBean; import net.osmand.plus.ApplicationMode.ApplicationModeBuilder; import net.osmand.plus.OsmandSettings.OsmandPreference; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.poi.PoiUIFilter; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionFactory; import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; @@ -33,11 +44,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +60,7 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; +import static net.osmand.IndexConstants.TILES_INDEX_DIR; /* Usage: @@ -83,6 +98,7 @@ public class SettingsHelper { private boolean importing; private boolean importSuspended; + private boolean collectOnly; private ImportAsyncTask importTask; public interface SettingsImportListener { @@ -103,7 +119,7 @@ public class SettingsHelper { public void setActivity(Activity activity) { this.activity = activity; - if (importing) { + if (importing && !collectOnly) { importTask.processNextItem(); } } @@ -128,6 +144,9 @@ public class SettingsHelper { PLUGIN, DATA, FILE, + QUICK_ACTION, + POI_UI_FILTERS, + MAP_SOURCES, } public abstract static class SettingsItem { @@ -157,6 +176,10 @@ public class SettingsHelper { @NonNull public abstract String getFileName(); + public Boolean shouldReadOnCollecting() { + return false; + } + static SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { return SettingsItemType.valueOf(json.getString("type")); } @@ -307,7 +330,6 @@ public class SettingsHelper { } } }); - } } @@ -419,6 +441,10 @@ public class SettingsHelper { appModeBeanPrefsIds = new HashSet<>(Arrays.asList(settings.appModeBeanPrefsIds)); } + public ApplicationMode getAppMode() { + return appMode; + } + @NonNull @Override public String getName() { @@ -472,7 +498,6 @@ public class SettingsHelper { json.put("appMode", new JSONObject(appMode.toJson())); } - @NonNull @Override SettingsItemReader getReader() { @@ -706,6 +731,443 @@ public class SettingsHelper { } } + public static class QuickActionSettingsItem extends OsmandSettingsItem { + + private List quickActions; + + private OsmandApplication app; + + public QuickActionSettingsItem(@NonNull OsmandApplication app, + @NonNull List quickActions) { + super(SettingsItemType.QUICK_ACTION, app.getSettings()); + this.app = app; + this.quickActions = quickActions; + } + + public QuickActionSettingsItem(@NonNull OsmandApplication app, + @NonNull JSONObject jsonObject) throws JSONException { + super(SettingsItemType.QUICK_ACTION, app.getSettings(), jsonObject); + this.app = app; + } + + public List getQuickActions() { + return quickActions; + } + + @Override + public void apply() { + if (!quickActions.isEmpty()) { + QuickActionFactory factory = new QuickActionFactory(); + List savedActions = factory.parseActiveActionsList(getSettings().QUICK_ACTION_LIST.get()); + List newActions = new ArrayList<>(savedActions); + for (QuickAction action : quickActions) { + for (QuickAction savedAction : savedActions) { + if (action.getName(app).equals(savedAction.getName(app))) { + newActions.remove(savedAction); + } + } + } + newActions.addAll(quickActions); + ((MapActivity) app.getSettingsHelper().getActivity()).getMapLayers().getQuickActionRegistry().updateQuickActions(newActions); + } + } + + @Override + public Boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public String getName() { + return "quick_actions"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "quick_actions"; + } + + @NonNull + @Override + public String getFileName() { + return getName() + ".json"; + } + + @NonNull + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + + } + + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + StringBuilder buf = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + } catch (IOException e) { + throw new IOException("Cannot read json body", e); + } + String jsonStr = buf.toString(); + if (Algorithms.isEmpty(jsonStr)) { + throw new IllegalArgumentException("Cannot find json body"); + } + final JSONObject json; + try { + quickActions = new ArrayList<>(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + json = new JSONObject(jsonStr); + JSONArray items = json.getJSONArray("items"); + for (int i = 0; i < items.length(); i++) { + JSONObject object = items.getJSONObject(i); + String name = object.getString("name"); + int actionType = object.getInt("type"); + String paramsString = object.getString("params"); + HashMap params = gson.fromJson(paramsString, type); + QuickAction quickAction = new QuickAction(actionType); + quickAction.setName(name); + quickAction.setParams(params); + quickActions.add(quickAction); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Json parse error", e); + } + } + }; + } + + @NonNull + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + JSONArray items = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + if (!quickActions.isEmpty()) { + for (QuickAction action : quickActions) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", action.getName(app)); + jsonObject.put("type", action.getType()); + jsonObject.put("params", gson.toJson(action.getParams(), type)); + items.put(jsonObject); + } + json.put("items", items); + } + } + }; + } + } + + public static class PoiUiFilterSettingsItem extends OsmandSettingsItem { + + private List poiUIFilters; + + private OsmandApplication app; + + public PoiUiFilterSettingsItem(OsmandApplication app, List poiUIFilters) { + super(SettingsItemType.POI_UI_FILTERS, app.getSettings()); + this.app = app; + this.poiUIFilters = poiUIFilters; + } + + public PoiUiFilterSettingsItem(OsmandApplication app, JSONObject jsonObject) throws JSONException { + super(SettingsItemType.POI_UI_FILTERS, app.getSettings(), jsonObject); + this.app = app; + } + + public List getPoiUIFilters() { + return this.poiUIFilters != null ? this.poiUIFilters : new ArrayList(); + } + + @Override + public void apply() { + if (!poiUIFilters.isEmpty()) { + for (PoiUIFilter filter : poiUIFilters) { + app.getPoiFilters().createPoiFilter(filter, false); + } + app.getSearchUICore().refreshCustomPoiFilters(); + } + } + + @NonNull + @Override + public String getName() { + return "poi_ui_filters"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return null; + } + + @Override + public Boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public String getFileName() { + return getName() + ".json"; + } + + @NonNull + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + + } + + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + StringBuilder buf = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + } catch (IOException e) { + throw new IOException("Cannot read json body", e); + } + String jsonStr = buf.toString(); + if (Algorithms.isEmpty(jsonStr)) { + throw new IllegalArgumentException("Cannot find json body"); + } + final JSONObject json; + try { + poiUIFilters = new ArrayList<>(); + json = new JSONObject(jsonStr); + JSONArray items = json.getJSONArray("items"); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + MapPoiTypes poiTypes = app.getPoiTypes(); + for (int i = 0; i < items.length(); i++) { + JSONObject object = items.getJSONObject(i); + String name = object.getString("name"); + String filterId = object.getString("filterId"); + String acceptedTypesString = object.getString("acceptedTypes"); + HashMap> acceptedTypes = gson.fromJson(acceptedTypesString, type); + Map> acceptedTypesDone = new HashMap<>(); + for (Map.Entry> mapItem : acceptedTypes.entrySet()) { + final PoiCategory a = poiTypes.getPoiCategoryByName(mapItem.getKey()); + acceptedTypesDone.put(a, mapItem.getValue()); + } + PoiUIFilter filter = new PoiUIFilter(name, filterId, acceptedTypesDone, app); + poiUIFilters.add(filter); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Json parse error", e); + } + } + }; + } + + @NonNull + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + JSONArray items = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + if (!poiUIFilters.isEmpty()) { + for (PoiUIFilter filter : poiUIFilters) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", filter.getName()); + jsonObject.put("filterId", filter.getFilterId()); + jsonObject.put("acceptedTypes", gson.toJson(filter.getAcceptedTypes(), type)); + items.put(jsonObject); + } + json.put("items", items); + } + } + }; + } + } + + public static class MapSourcesSettingsItem extends OsmandSettingsItem { + + private OsmandApplication app; + + private List mapSources; + + public MapSourcesSettingsItem(OsmandApplication app, List mapSources) { + super(SettingsItemType.MAP_SOURCES, app.getSettings()); + this.app = app; + this.mapSources = mapSources; + } + + public MapSourcesSettingsItem(OsmandApplication app, JSONObject jsonObject) throws JSONException { + super(SettingsItemType.MAP_SOURCES, app.getSettings(), jsonObject); + this.app = app; + } + + public List getMapSources() { + return this.mapSources; + } + + @Override + public void apply() { + if (!mapSources.isEmpty()) { + for (ITileSource template : mapSources) { + if (template instanceof TileSourceManager.TileSourceTemplate) { + getSettings().installTileSource((TileSourceManager.TileSourceTemplate) template); + } else { + ((SQLiteTileSource) template).createDataBase(); + } + } + } + } + + @NonNull + @Override + public String getName() { + return "map_sources"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return null; + } + + @Override + public Boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public String getFileName() { + return getName() + ".json"; + } + + @NonNull + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + + } + + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + StringBuilder buf = new StringBuilder(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String str; + while ((str = in.readLine()) != null) { + buf.append(str); + } + } catch (IOException e) { + throw new IOException("Cannot read json body", e); + } + String jsonStr = buf.toString(); + if (Algorithms.isEmpty(jsonStr)) { + throw new IllegalArgumentException("Cannot find json body"); + } + final JSONObject json; + try { + mapSources = new ArrayList<>(); + json = new JSONObject(jsonStr); + JSONArray items = json.getJSONArray("items"); + for (int i = 0; i < items.length(); i++) { + JSONObject object = items.getJSONObject(i); + boolean sql = object.optBoolean("sql"); + String name = object.optString("name"); + int minZoom = object.optInt("minZoom"); + int maxZoom = object.optInt("maxZoom"); + String url = object.optString("url"); + String randoms = object.optString("randoms"); + boolean ellipsoid = object.optBoolean("ellipsoid", false); + boolean invertedY = object.optBoolean("inverted_y", false); + String referer = object.optString("referer"); + boolean timesupported = object.optBoolean("timesupported", false); + long expire = object.optLong("expire"); + boolean inversiveZoom = object.optBoolean("inversiveZoom", false); + + String ext = object.optString("ext"); + int tileSize = object.optInt("tileSize"); + int bitDensity = object.optInt("bitDensity"); + int avgSize = object.optInt("avgSize"); + String rule = object.optString("rule"); + + ITileSource template; + if (!sql) { + template = new TileSourceManager.TileSourceTemplate(name, url, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); + } else { + template = new SQLiteTileSource(app, name, minZoom, maxZoom, url, randoms, ellipsoid, invertedY, referer, timesupported, expire, inversiveZoom); + } + mapSources.add(template); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Json parse error", e); + } + } + }; + } + + @NonNull + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + JSONArray items = new JSONArray(); + if (!mapSources.isEmpty()) { + for (ITileSource template : mapSources) { + JSONObject jsonObject = new JSONObject(); + boolean sql = template instanceof SQLiteTileSource; + jsonObject.put("sql", sql); + jsonObject.put("name", template.getName()); + jsonObject.put("minZoom", template.getMinimumZoomSupported()); + jsonObject.put("maxZoom", template.getMaximumZoomSupported()); + jsonObject.put("url", template.getUrlTemplate()); + jsonObject.put("randoms", template.getRandoms()); + jsonObject.put("ellipsoid", template.isEllipticYTile()); + jsonObject.put("inverted_y", template.isInvertedYTile()); + jsonObject.put("referer", template.getReferer()); + jsonObject.put("timesupported", template.isTimeSupported()); + jsonObject.put("expire", template.getExpirationTimeMillis()); + jsonObject.put("inversiveZoom", template.getInversiveZoom()); + + jsonObject.put("ext", template.getTileFormat()); + jsonObject.put("tileSize", template.getTileSize()); + jsonObject.put("bitDensity", template.getBitDensity()); + jsonObject.put("avgSize", template.getAvgSize()); + jsonObject.put("rule", template.getRule()); + + items.put(jsonObject); + } + json.put("items", items); + } + } + }; + } + } + private static class SettingsItemsFactory { private OsmandApplication app; @@ -762,6 +1224,15 @@ public class SettingsHelper { case FILE: item = new FileSettingsItem(app, json); break; + case QUICK_ACTION: + item = new QuickActionSettingsItem(app, json); + break; + case POI_UI_FILTERS: + item = new PoiUiFilterSettingsItem(app, json); + break; + case MAP_SOURCES: + item = new MapSourcesSettingsItem(app, json); + break; } return item; } @@ -873,10 +1344,11 @@ public class SettingsHelper { LOG.error("Error parsing items: " + itemsJson, e); throw new IllegalArgumentException("No items"); } - while (!collecting && (entry = zis.getNextEntry()) != null) { + while ((entry = zis.getNextEntry()) != null) { String fileName = entry.getName(); SettingsItem item = itemsFactory.getItemByFileName(fileName); - if (item != null) { + if (item != null && collecting && item.shouldReadOnCollecting() + || item != null && !collecting && !item.shouldReadOnCollecting()) { try { item.getReader().readFromStream(ois); } catch (IllegalArgumentException e) { @@ -924,6 +1396,17 @@ public class SettingsHelper { this.version = version; this.askBeforeImport = askBeforeImport; importer = new SettingsImporter(app); + collectOnly = true; + } + + ImportAsyncTask(@NonNull File settingsFile, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener listener) { + this.file = settingsFile; + this.listener = listener; + this.items = items; + this.latestChanges = latestChanges; + this.version = version; + importer = new SettingsImporter(app); + collectOnly = false; } @Override @@ -938,12 +1421,16 @@ public class SettingsHelper { @Override protected List doInBackground(Void... voids) { - try { - return importer.collectItems(file); - } catch (IllegalArgumentException e) { - LOG.error("Failed to collect items from: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to collect items from: " + file.getName(), e); + if (collectOnly) { + try { + return importer.collectItems(file); + } catch (IllegalArgumentException e) { + LOG.error("Failed to collect items from: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to collect items from: " + file.getName(), e); + } + } else { + return this.items; } return null; } @@ -951,8 +1438,12 @@ public class SettingsHelper { @Override protected void onPostExecute(List items) { this.items = items; - if (items != null && items.size() > 0) { - processNextItem(); + if (collectOnly) { + listener.onSettingsImportFinished(true, false, items); + } else { + if (items != null && items.size() > 0) { + processNextItem(); + } } } @@ -1045,6 +1536,24 @@ public class SettingsHelper { processedItems.add(item); processNextItem(); } + + public List getItems() { + return this.items; + } + + public File getFile() { + return this.file; + } + } + + @Nullable + public List getSettingsItems() { + return this.importTask.getItems(); + } + + @Nullable + public File getSettingsFile() { + return this.importTask.getFile(); } @SuppressLint("StaticFieldLeak") @@ -1136,6 +1645,10 @@ public class SettingsHelper { new ImportAsyncTask(settingsFile, latestChanges, version, askBeforeImport, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void importSettings(@NonNull File settingsFile, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener listener) { + new ImportAsyncTask(settingsFile, items, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, @NonNull List items) { @@ -1147,4 +1660,4 @@ public class SettingsHelper { @NonNull SettingsItem... items) { exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items))); } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/helpers/ImportHelper.java b/OsmAnd/src/net/osmand/plus/helpers/ImportHelper.java index 7cc1f5f337..64b18a4d02 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/ImportHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/ImportHelper.java @@ -15,6 +15,7 @@ import android.provider.OpenableColumns; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.style.ForegroundColorSpan; @@ -33,6 +34,7 @@ import net.osmand.data.FavouritePoint; import net.osmand.plus.AppInitializer; import net.osmand.plus.AppInitializer.AppInitializeListener; import net.osmand.plus.AppInitializer.InitEvents; +import net.osmand.plus.ApplicationMode; import net.osmand.plus.FavouritesDbHelper; import net.osmand.plus.GPXDatabase; import net.osmand.plus.OsmandApplication; @@ -49,6 +51,9 @@ import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerHalfItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.ShortDescriptionItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; +import net.osmand.plus.profiles.AdditionalDataWrapper; +import net.osmand.plus.profiles.ExportImportProfileBottomSheet; +import net.osmand.plus.quickaction.QuickAction; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.settings.ImportDuplicatesFragment; import net.osmand.plus.settings.ImportSettingsFragment; @@ -770,7 +775,7 @@ public class ImportHelper { @Override protected void onPostExecute(String error) { File tempDir = app.getAppPath(IndexConstants.TEMP_DIR); - File file = new File(tempDir, name); + final File file = new File(tempDir, name); if (error == null && file.exists()) { app.getSettingsHelper().importSettings(file, latestChanges, version, askBeforeImport, new SettingsImportListener() { @Override @@ -779,11 +784,15 @@ public class ImportHelper { progress.dismiss(); } if (succeed) { - app.showShortToastMessage(app.getString(R.string.file_imported_successfully, name)); - if (callback != null) { - callback.processResult(items); + FragmentManager fragmentManager = activity.getSupportFragmentManager(); + if (fragmentManager != null) { + ExportImportProfileBottomSheet.showInstance( + fragmentManager, + ExportImportProfileBottomSheet.State.IMPORT, + file, + items); } - } else if (!empty) { + } else { app.showShortToastMessage(app.getString(R.string.file_import_error, name, app.getString(R.string.shared_string_unexpected_error))); } } diff --git a/OsmAnd/src/net/osmand/plus/profiles/AdditionalDataWrapper.java b/OsmAnd/src/net/osmand/plus/profiles/AdditionalDataWrapper.java new file mode 100644 index 0000000000..bb46a92393 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/profiles/AdditionalDataWrapper.java @@ -0,0 +1,35 @@ +package net.osmand.plus.profiles; + +import java.util.List; + +public class AdditionalDataWrapper { + + private Type type; + + private List items; + + public AdditionalDataWrapper(Type type, List items) { + this.type = type; + this.items = items; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public List getItems() { + return items; + } + + public enum Type { + QUICK_ACTIONS, + POI_TYPES, + MAP_SOURCES, + CUSTOM_RENDER_STYLE, + CUSTOM_ROUTING + } +} diff --git a/OsmAnd/src/net/osmand/plus/profiles/ExportImportProfileBottomSheet.java b/OsmAnd/src/net/osmand/plus/profiles/ExportImportProfileBottomSheet.java new file mode 100644 index 0000000000..8a14422a58 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/profiles/ExportImportProfileBottomSheet.java @@ -0,0 +1,711 @@ +package net.osmand.plus.profiles; + +import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.CompoundButtonCompat; +import android.support.v7.widget.SwitchCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ExpandableListView; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import net.osmand.AndroidUtils; +import net.osmand.IndexConstants; +import net.osmand.PlatformUtil; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.plus.ApplicationMode; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.plus.SettingsHelper; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.OsmandBaseExpandableListAdapter; +import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithCompoundButton; +import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription; +import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; +import net.osmand.plus.poi.PoiUIFilter; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionFactory; +import net.osmand.plus.render.RenderingIcons; +import net.osmand.plus.settings.BaseSettingsFragment; +import net.osmand.plus.settings.bottomsheets.BasePreferenceBottomSheet; + +import org.apache.commons.logging.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ExportImportProfileBottomSheet extends BasePreferenceBottomSheet { + + private static final Log LOG = PlatformUtil.getLog(ExportImportProfileBottomSheet.class); + + public static final String TAG = ExportImportProfileBottomSheet.class.getSimpleName(); + + private static final String STATE_KEY = "EXPORT_IMPORT_DIALOG_STATE_KEY"; + + private static final String INCLUDE_ADDITIONAL_DATA_KEY = "INCLUDE_ADDITIONAL_DATA_KEY"; + + private boolean includeAdditionalData = false; + + private boolean containsAdditionalData = false; + + private OsmandApplication app; + + private ApplicationMode profile; + + private State state; + + private List dataList = new ArrayList<>(); + + private List dataToOperate = new ArrayList<>(); + + private List settingsItems; + + private ExpandableListView listView; + + private ProfileAdditionalDataAdapter adapter; + + private SettingsHelper.ProfileSettingsItem profileSettingsItem; + + private File file; + + @Override + public void onCreate(Bundle savedInstanceState) { + if (savedInstanceState != null) { + includeAdditionalData = savedInstanceState.getBoolean(INCLUDE_ADDITIONAL_DATA_KEY); + } + super.onCreate(savedInstanceState); + app = requiredMyApplication(); + Bundle bundle = getArguments(); + if (bundle != null) { + this.state = (State) getArguments().getSerializable(STATE_KEY); + } + if (state == State.IMPORT) { + if (settingsItems == null) { + settingsItems = app.getSettingsHelper().getSettingsItems(); + } + if (file == null) { + file = app.getSettingsHelper().getSettingsFile(); + } + containsAdditionalData = checkAdditionalDataContains(); + } else { + dataList = getAdditionalData(); + for (AdditionalDataWrapper dataWrapper : dataList) { + dataToOperate.addAll(dataWrapper.getItems()); + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(INCLUDE_ADDITIONAL_DATA_KEY, includeAdditionalData); + } + + @Override + public void createMenuItems(Bundle savedInstanceState) { + final Context context = getContext(); + if (context == null) { + return; + } + LayoutInflater inflater = UiUtilities.getInflater(app, nightMode); + + profile = state == State.IMPORT ? getAppModeFromSettingsItems() : getAppMode(); + + int profileColor = profile.getIconColorInfo().getColor(nightMode); + int colorNoAlpha = ContextCompat.getColor(context, profileColor); + + Drawable backgroundIcon = UiUtilities.getColoredSelectableDrawable(context, colorNoAlpha, 0.3f); + Drawable[] layers = {new ColorDrawable(UiUtilities.getColorWithAlpha(colorNoAlpha, 0.10f)), backgroundIcon}; + + items.add(new TitleItem(state == State.EXPORT ? + getString(R.string.export_profile) + : getString(R.string.import_profile))); + + BaseBottomSheetItem profileItem = new BottomSheetItemWithCompoundButton.Builder() + .setChecked(true) + .setCompoundButtonColorId(profileColor) + .setButtonTintList(ColorStateList.valueOf(getResolvedColor(profileColor))) + .setDescription(BaseSettingsFragment.getAppModeDescription(context, profile)) + .setIcon(getIcon(profile.getIconRes(), profileColor)) + .setTitle(profile.toHumanString()) + .setBackground(new LayerDrawable(layers)) + .setLayoutId(R.layout.preference_profile_item_with_radio_btn) + .create(); + items.add(profileItem); + + if (state == State.IMPORT && containsAdditionalData || state == State.EXPORT && !dataList.isEmpty()) { + BaseBottomSheetItem descriptionItem = new BottomSheetItemWithDescription.Builder() + .setDescription(state == State.EXPORT ? + getString(R.string.export_profile_dialog_description) + : getString(R.string.import_profile_dialog_description)) + .setLayoutId(R.layout.bottom_sheet_item_pref_info) + .create(); + items.add(descriptionItem); + + final View additionalDataView = inflater.inflate(R.layout.bottom_sheet_item_additional_data, null); + listView = additionalDataView.findViewById(R.id.list); + SwitchCompat switchItem = additionalDataView.findViewById(R.id.switchItem); + switchItem.setTextColor(getResources().getColor(nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light)); + switchItem.setChecked(includeAdditionalData); + switchItem.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + includeAdditionalData = !includeAdditionalData; + listView.setVisibility(includeAdditionalData ? + View.VISIBLE : View.GONE); + if (includeAdditionalData && state == State.IMPORT) { + updateDataToOperateFromSettingsItems(); + } + setupHeightAndBackground(getView()); + } + }); + listView.setVisibility(includeAdditionalData ? View.VISIBLE : View.GONE); + adapter = new ProfileAdditionalDataAdapter(app, dataList, profileColor); + listView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() { + @Override + public void onGroupExpand(int i) { + setupHeightAndBackground(getView()); + } + }); + listView.setAdapter(adapter); + final SimpleBottomSheetItem titleItem = (SimpleBottomSheetItem) new SimpleBottomSheetItem.Builder() + .setCustomView(additionalDataView) + .create(); + items.add(titleItem); + } + } + + @Override + protected int getRightBottomButtonTextId() { + return state == State.EXPORT ? R.string.shared_string_export : R.string.shared_string_import; + } + + @Override + protected void onRightBottomButtonClick() { + super.onRightBottomButtonClick(); + if (state == State.EXPORT) { + prepareFile(); + } else { + importSettings(); + } + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected boolean useScrollableItemsContainer() { + return false; + } + + private ApplicationMode getAppModeFromSettingsItems() { + for (SettingsHelper.SettingsItem item : settingsItems) { + if (item.getType().equals(SettingsHelper.SettingsItemType.PROFILE)) { + profileSettingsItem = ((SettingsHelper.ProfileSettingsItem) item); + return ((SettingsHelper.ProfileSettingsItem) item).getAppMode(); + } + } + return getAppMode(); + } + + private List getAdditionalData() { + List dataList = new ArrayList<>(); + + QuickActionFactory factory = new QuickActionFactory(); + List actionsList = factory.parseActiveActionsList(app.getSettings().QUICK_ACTION_LIST.get()); + if (!actionsList.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.QUICK_ACTIONS, actionsList)); + } + + List poiList = app.getPoiFilters().getUserDefinedPoiFilters(false); + if (!poiList.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.POI_TYPES, + poiList + )); + } + + List iTileSources = new ArrayList<>(); + final LinkedHashMap tileSourceEntries = new LinkedHashMap<>(app.getSettings().getTileSourceEntries(true)); + for (Map.Entry entry : tileSourceEntries.entrySet()) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + entry.getKey()); + if (f != null) { + ITileSource template; + if (f.getName().endsWith(SQLiteTileSource.EXT)) { + template = new SQLiteTileSource(app, f, TileSourceManager.getKnownSourceTemplates()); + } else { + template = TileSourceManager.createTileSourceTemplate(f); + } + if (template != null && template.getUrlTemplate() != null) { + iTileSources.add(template); + } + } + } + if (!iTileSources.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.MAP_SOURCES, + iTileSources + )); + } + + Map externalRenderers = app.getRendererRegistry().getExternalRenderers(); + if (!externalRenderers.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.CUSTOM_RENDER_STYLE, + new ArrayList<>(externalRenderers.values()) + )); + } + + File routingProfilesFolder = app.getAppPath(IndexConstants.ROUTING_PROFILES_DIR); + if (routingProfilesFolder.exists() && routingProfilesFolder.isDirectory()) { + File[] fl = routingProfilesFolder.listFiles(); + if (fl != null && fl.length > 0) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.CUSTOM_ROUTING, + Arrays.asList(fl) + )); + } + } + + return dataList; + } + + private List prepareSettingsItemsForExport() { + List settingsItems = new ArrayList<>(); + settingsItems.add(new SettingsHelper.ProfileSettingsItem(app.getSettings(), profile)); + if (includeAdditionalData) { + settingsItems.addAll(prepareAdditionalSettingsItems()); + } + return settingsItems; + } + + private List prepareAdditionalSettingsItems() { + List settingsItems = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + for (Object object : dataToOperate) { + if (object instanceof QuickAction) { + quickActions.add((QuickAction) object); + } else if (object instanceof PoiUIFilter) { + poiUIFilters.add((PoiUIFilter) object); + } else if (object instanceof TileSourceManager.TileSourceTemplate + || object instanceof SQLiteTileSource) { + tileSourceTemplates.add((ITileSource) object); + } else if (object instanceof File) { + settingsItems.add(new SettingsHelper.FileSettingsItem(app, (File) object)); + } + } + if (!quickActions.isEmpty()) { + settingsItems.add(new SettingsHelper.QuickActionSettingsItem(app, quickActions)); + } + if (!poiUIFilters.isEmpty()) { + settingsItems.add(new SettingsHelper.PoiUiFilterSettingsItem(app, poiUIFilters)); + } + if (!tileSourceTemplates.isEmpty()) { + settingsItems.add(new SettingsHelper.MapSourcesSettingsItem(app, tileSourceTemplates)); + } + return settingsItems; + } + + private Boolean checkAdditionalDataContains() { + boolean containsData = false; + for (SettingsHelper.SettingsItem item : settingsItems) { + containsData = item.getType().equals(SettingsHelper.SettingsItemType.QUICK_ACTION) + || item.getType().equals(SettingsHelper.SettingsItemType.POI_UI_FILTERS) + || item.getType().equals(SettingsHelper.SettingsItemType.MAP_SOURCES) + || item.getType().equals(SettingsHelper.SettingsItemType.FILE); + if (containsData) { + break; + } + } + return containsData; + } + + private void updateDataToOperateFromSettingsItems() { + List dataList = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List routingFilesList = new ArrayList<>(); + List renderFilesList = new ArrayList<>(); + + for (SettingsHelper.SettingsItem item : settingsItems) { + if (item.getType().equals(SettingsHelper.SettingsItemType.QUICK_ACTION)) { + quickActions.addAll(((SettingsHelper.QuickActionSettingsItem) item).getQuickActions()); + } else if (item.getType().equals(SettingsHelper.SettingsItemType.POI_UI_FILTERS)) { + poiUIFilters.addAll(((SettingsHelper.PoiUiFilterSettingsItem) item).getPoiUIFilters()); + } else if (item.getType().equals(SettingsHelper.SettingsItemType.MAP_SOURCES)) { + tileSourceTemplates.addAll(((SettingsHelper.MapSourcesSettingsItem) item).getMapSources()); + } else if (item.getType().equals(SettingsHelper.SettingsItemType.FILE)) { + if (item.getName().startsWith("/rendering/")) { + renderFilesList.add(((SettingsHelper.FileSettingsItem) item).getFile()); + } else if (item.getName().startsWith("/routing/")) { + routingFilesList.add(((SettingsHelper.FileSettingsItem) item).getFile()); + } + } + } + + if (!quickActions.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.QUICK_ACTIONS, + quickActions)); + dataToOperate.addAll(quickActions); + } + if (!poiUIFilters.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.POI_TYPES, + poiUIFilters)); + dataToOperate.addAll(poiUIFilters); + } + if (!tileSourceTemplates.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.MAP_SOURCES, + tileSourceTemplates + )); + dataToOperate.addAll(tileSourceTemplates); + } + if (!renderFilesList.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.CUSTOM_RENDER_STYLE, + renderFilesList + )); + dataToOperate.addAll(renderFilesList); + } + if (!routingFilesList.isEmpty()) { + dataList.add(new AdditionalDataWrapper( + AdditionalDataWrapper.Type.CUSTOM_ROUTING, + routingFilesList + )); + dataToOperate.addAll(routingFilesList); + } + adapter.updateList(dataList); + } + + private void importSettings() { + List list = new ArrayList<>(); + list.add(profileSettingsItem); + if (includeAdditionalData) { + list.addAll(prepareAdditionalSettingsItems()); + } + app.getSettingsHelper().importSettings(file, list, "", 1, new SettingsHelper.SettingsImportListener() { + @Override + public void onSettingsImportFinished(boolean succeed, boolean empty, @NonNull List items) { + if (succeed) { + app.showShortToastMessage(app.getString(R.string.file_imported_successfully, file.getName())); + } else if (empty) { + app.showShortToastMessage(app.getString(R.string.file_import_error, file.getName(), app.getString(R.string.shared_string_unexpected_error))); + } + } + }); + dismiss(); + } + + private void prepareFile() { + if (app != null) { + File tempDir = app.getAppPath(IndexConstants.TEMP_DIR); + if (!tempDir.exists()) { + tempDir.mkdirs(); + } + String fileName = profile.toHumanString(); + app.getSettingsHelper().exportSettings(tempDir, fileName, new SettingsHelper.SettingsExportListener() { + @Override + public void onSettingsExportFinished(@NonNull File file, boolean succeed) { + if (succeed) { + shareProfile(file, profile); + } else { + app.showToastMessage(R.string.export_profile_failed); + } + } + }, prepareSettingsItemsForExport()); + } + } + + private void shareProfile(@NonNull File file, @NonNull ApplicationMode profile) { + try { + final Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.exported_osmand_profile, profile.toHumanString())); + sendIntent.putExtra(Intent.EXTRA_STREAM, AndroidUtils.getUriForFile(getMyApplication(), file)); + sendIntent.setType("*/*"); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(sendIntent); + dismiss(); + } catch (Exception e) { + Toast.makeText(requireContext(), R.string.export_profile_failed, Toast.LENGTH_SHORT).show(); + LOG.error("Share profile error", e); + } + } + + public static boolean showInstance(@NonNull FragmentManager fragmentManager, + State state, + Fragment target, + @NonNull ApplicationMode appMode) { + try { + Bundle bundle = new Bundle(); + bundle.putSerializable(STATE_KEY, state); + ExportImportProfileBottomSheet fragment = new ExportImportProfileBottomSheet(); + fragment.setArguments(bundle); + fragment.setAppMode(appMode); + fragment.setTargetFragment(target, 0); + fragment.show(fragmentManager, TAG); + return true; + } catch (RuntimeException e) { + return false; + } + } + + public static boolean showInstance(@NonNull FragmentManager fragmentManager, + State state, + File file, + List items) { + try { + Bundle bundle = new Bundle(); + bundle.putSerializable(STATE_KEY, state); + ExportImportProfileBottomSheet fragment = new ExportImportProfileBottomSheet(); + fragment.setArguments(bundle); + fragment.setSettingsItems(items); + fragment.setFile(file); + fragment.show(fragmentManager, TAG); + return true; + } catch (RuntimeException e) { + return false; + } + } + + public void setSettingsItems(List settingsItems) { + this.settingsItems = settingsItems; + } + + public File getFile() { + return file; + } + + public void setFile(File file) { + this.file = file; + } + + public enum State { + EXPORT, + IMPORT + } + + class ProfileAdditionalDataAdapter extends OsmandBaseExpandableListAdapter { + + private OsmandApplication app; + + private List list; + + private int profileColor; + + ProfileAdditionalDataAdapter(OsmandApplication app, List list, int profileColor) { + this.app = app; + this.list = list; + this.profileColor = profileColor; + } + + public void updateList(List list) { + this.list = list; + notifyDataSetChanged(); + } + + @Override + public int getGroupCount() { + return list.size(); + } + + @Override + public int getChildrenCount(int i) { + return list.get(i).getItems().size(); + } + + @Override + public Object getGroup(int i) { + return list.get(i); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return list.get(groupPosition).getItems().get(childPosition); + } + + @Override + public long getGroupId(int i) { + return i; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return groupPosition * 10000 + childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + View group = convertView; + if (group == null) { + LayoutInflater inflater = UiUtilities.getInflater(app, nightMode); + group = inflater.inflate(R.layout.profile_data_list_item_group, parent, false); + } + + boolean isLastGroup = groupPosition == getGroupCount() - 1; + final AdditionalDataWrapper.Type type = list.get(groupPosition).getType(); + + TextView titleTv = group.findViewById(R.id.title_tv); + TextView subTextTv = group.findViewById(R.id.sub_text_tv); + final CheckBox checkBox = group.findViewById(R.id.check_box); + ImageView expandIv = group.findViewById(R.id.explist_indicator); + View divider = group.findViewById(R.id.divider); + + titleTv.setText(getGroupTitle(type)); + divider.setVisibility(isExpanded || isLastGroup ? View.GONE : View.VISIBLE); + CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, profileColor))); + + final List listItems = list.get(groupPosition).getItems(); + subTextTv.setText(String.valueOf(listItems.size())); + + checkBox.setChecked(dataToOperate.containsAll(listItems)); + checkBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + if (checkBox.isChecked()) { + for (Object object : listItems) { + if (!dataToOperate.contains(object)) { + dataToOperate.add(object); + } + } + } else { + dataToOperate.removeAll(listItems); + } + notifyDataSetInvalidated(); + } + }); + + adjustIndicator(app, groupPosition, isExpanded, group, true); + + return group; + } + + @Override + public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + View child = convertView; + if (child == null) { + LayoutInflater inflater = UiUtilities.getInflater(app, nightMode); + child = inflater.inflate(R.layout.profile_data_list_item_child, parent, false); + } + final Object currentItem = list.get(groupPosition).getItems().get(childPosition); + + + boolean isLastGroup = groupPosition == getGroupCount() - 1; + final AdditionalDataWrapper.Type type = list.get(groupPosition).getType(); + + TextView title = child.findViewById(R.id.title_tv); + final CheckBox checkBox = child.findViewById(R.id.check_box); + ImageView icon = child.findViewById(R.id.icon); + View divider = child.findViewById(R.id.divider); + + divider.setVisibility(isLastChild && !isLastGroup ? View.VISIBLE : View.GONE); + CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, profileColor))); + + checkBox.setChecked(dataToOperate.contains(currentItem)); + checkBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (checkBox.isChecked()) { + dataToOperate.add(currentItem); + } else { + dataToOperate.remove(currentItem); + } + notifyDataSetInvalidated(); + } + }); + + switch (type) { + case QUICK_ACTIONS: + title.setText(((QuickAction) currentItem).getName(app.getApplicationContext())); + icon.setVisibility(View.INVISIBLE); + icon.setImageResource(R.drawable.ic_action_info_dark); + break; + case POI_TYPES: + title.setText(((PoiUIFilter) currentItem).getName()); + icon.setVisibility(View.VISIBLE); + int iconRes = RenderingIcons.getBigIconResourceId(((PoiUIFilter) currentItem).getIconId()); + icon.setImageDrawable(app.getUIUtilities().getIcon(iconRes != 0 ? iconRes : R.drawable.ic_person, profileColor)); + break; + case MAP_SOURCES: + title.setText(((ITileSource) currentItem).getName()); + icon.setVisibility(View.INVISIBLE); + icon.setImageResource(R.drawable.ic_action_info_dark); + break; + case CUSTOM_RENDER_STYLE: + String renderName = ((File) currentItem).getName(); + renderName = renderName.replace('_', ' ').replaceAll(".render.xml", ""); + title.setText(renderName); + icon.setVisibility(View.INVISIBLE); + icon.setImageResource(R.drawable.ic_action_info_dark); + break; + case CUSTOM_ROUTING: + String routingName = ((File) currentItem).getName(); + routingName = routingName.replace('_', ' ').replaceAll(".xml", ""); + title.setText(routingName); + icon.setVisibility(View.INVISIBLE); + icon.setImageResource(R.drawable.ic_action_info_dark); + break; + default: + return child; + } + return child; + } + + @Override + public boolean isChildSelectable(int i, int i1) { + return false; + } + + private int getGroupTitle(AdditionalDataWrapper.Type type) { + switch (type) { + case QUICK_ACTIONS: + return R.string.configure_screen_quick_action; + case POI_TYPES: + return R.string.poi_dialog_poi_type; + case MAP_SOURCES: + return R.string.quick_action_map_source_title; + case CUSTOM_RENDER_STYLE: + return R.string.shared_string_custom_rendering_style; + case CUSTOM_ROUTING: + return R.string.shared_string_routing; + default: + return R.string.access_empty_list; + } + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/quickaction/QuickAction.java b/OsmAnd/src/net/osmand/plus/quickaction/QuickAction.java index 220cde20cf..ccde8f0dee 100644 --- a/OsmAnd/src/net/osmand/plus/quickaction/QuickAction.java +++ b/OsmAnd/src/net/osmand/plus/quickaction/QuickAction.java @@ -41,7 +41,7 @@ public class QuickAction { this.type = type; } - protected QuickAction(int type) { + public QuickAction(int type) { this.id = System.currentTimeMillis(); this.type = type; this.nameRes = QuickActionFactory.getActionName(type); diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index dab94170ed..64d091269c 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -343,4 +343,8 @@ public class RendererRegistry { } return null; } + + public Map getExternalRenderers() { + return externalRenderers; + } } diff --git a/OsmAnd/src/net/osmand/plus/settings/ConfigureProfileFragment.java b/OsmAnd/src/net/osmand/plus/settings/ConfigureProfileFragment.java index fafa908a45..50b5f82f7e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/ConfigureProfileFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/ConfigureProfileFragment.java @@ -41,6 +41,7 @@ import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.openseamapsplugin.NauticalMapsPlugin; +import net.osmand.plus.profiles.ExportImportProfileBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; @@ -395,23 +396,14 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co ResetProfilePrefsBottomSheet.showInstance(fragmentManager, prefId, this, false, getSelectedAppMode()); } } else if (EXPORT_PROFILE.equals(prefId)) { - Context ctx = requireContext(); - final ApplicationMode profile = getSelectedAppMode(); - File tempDir = app.getAppPath(IndexConstants.TEMP_DIR); - if (!tempDir.exists()) { - tempDir.mkdirs(); + FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager != null) { + ExportImportProfileBottomSheet.showInstance( + fragmentManager, + ExportImportProfileBottomSheet.State.EXPORT, + this, + getSelectedAppMode()); } - String fileName = profile.toHumanString(); - app.getSettingsHelper().exportSettings(tempDir, fileName, new SettingsHelper.SettingsExportListener() { - @Override - public void onSettingsExportFinished(@NonNull File file, boolean succeed) { - if (succeed) { - shareProfile(file, profile); - } else { - app.showToastMessage(R.string.export_profile_failed); - } - } - }, new ProfileSettingsItem(app.getSettings(), profile)); } else if (DELETE_PROFILE.equals(prefId)) { onDeleteProfileClick(); }