From e2fb0992b7baa431ddaec2c684164bf7ee4799dc Mon Sep 17 00:00:00 2001 From: sonora Date: Mon, 8 Jun 2020 22:03:46 +0200 Subject: [PATCH 001/123] disable debug libraries --- OsmAnd/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 6bebf5f67b..fab8755326 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -217,6 +217,9 @@ android { buildTypes { debug { signingConfig signingConfigs.development + debuggable false + jniDebuggable false + buildConfigField "boolean", "USE_DEBUG_LIBRARIES", "false" } release { signingConfig signingConfigs.publishing From 6941d455161d57e3f23f8905aaea4da6496f788a Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Fri, 9 Oct 2020 17:53:57 +0300 Subject: [PATCH 002/123] Plan Route Add Graphs initial commit --- .../res/layout/fragment_measurement_tool.xml | 34 +- .../fragment_measurement_tool_graph.xml | 111 +++++ .../fragment_measurement_tool_points_list.xml | 24 + OsmAnd/res/values/sizes.xml | 1 + OsmAnd/res/values/strings.xml | 2 + .../plus/activities/SettingsBaseActivity.java | 21 +- .../editors/PointEditorFragmentNew.java | 8 +- .../other/HorizontalSelectionAdapter.java | 98 ++++- .../MeasurementEditingContext.java | 66 +++ .../MeasurementToolFragment.java | 296 +++++++------ .../plus/measurementtool/MtGraphFragment.java | 414 ++++++++++++++++++ .../measurementtool/MtPointsFragment.java | 70 +++ .../SelectFileBottomSheet.java | 11 +- .../cards/TracksToFollowCard.java | 10 +- .../VehicleParametersBottomSheet.java | 10 +- 15 files changed, 994 insertions(+), 182 deletions(-) create mode 100644 OsmAnd/res/layout/fragment_measurement_tool_graph.xml create mode 100644 OsmAnd/res/layout/fragment_measurement_tool_points_list.xml create mode 100644 OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java create mode 100644 OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java diff --git a/OsmAnd/res/layout/fragment_measurement_tool.xml b/OsmAnd/res/layout/fragment_measurement_tool.xml index fe1ba9b1d7..1ccae4f0c3 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool.xml @@ -130,33 +130,25 @@ tools:text="@string/add_point_after"/> - + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone" > - + - + android:layout_height="@dimen/content_padding_small" /> - - - - + android:layout_height="wrap_content" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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/values/sizes.xml b/OsmAnd/res/values/sizes.xml index f4eb13a389..69f711b6fb 100644 --- a/OsmAnd/res/values/sizes.xml +++ b/OsmAnd/res/values/sizes.xml @@ -324,6 +324,7 @@ 8dp 18dp 71dp + 120dp 40dp 48dp 18dp diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index d6d400bd8f..9d8aa0e70c 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,8 @@ Thx - Hardy --> + Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it. + Graph Logout successful Clear OpenStreetMap OAuth token Log in via OAuth diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java index 316de777b0..1750787774 100644 --- a/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java @@ -189,11 +189,9 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity if(propertyValue == null) { return ""; } - final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_"); - Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name"); - if (f != null) { - Integer in = (Integer) f.get(null); - return ctx.getString(in); + int valueId = getStringRouteInfoPropertyValueId(propertyValue); + if (valueId != -1) { + return ctx.getString(valueId); } } catch (Exception e) { System.err.println(e.getMessage()); @@ -201,6 +199,19 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity return propertyValue; } + public static int getStringRouteInfoPropertyValueId(String propertyValue) { + try { + final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_"); + Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name"); + if (f != null) { + return (Integer) f.get(null); + } + } catch (Exception e) { + System.err.println(e.getMessage()); + } + return -1; + } + public void registerListPreference(OsmandPreference b, PreferenceGroup screen, String[] names, T[] values) { ListPreference p = (ListPreference) screen.findPreference(b.getId()); prepareListPreference(b, names, values, p); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java index 7500a17d77..0bc215c366 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java @@ -618,12 +618,12 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { } } HorizontalSelectionAdapter horizontalSelectionAdapter = new HorizontalSelectionAdapter(app, nightMode); - horizontalSelectionAdapter.setItems(new ArrayList<>(iconCategories.keySet())); - horizontalSelectionAdapter.setSelectedItem(selectedIconCategory); + horizontalSelectionAdapter.setTitledItems(new ArrayList<>(iconCategories.keySet())); + horizontalSelectionAdapter.setSelectedItemByTitle(selectedIconCategory); horizontalSelectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedIconCategory = item; + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedIconCategory = item.getTitle(); createIconForCategory(); updateIconSelector(selectedIcon, PointEditorFragmentNew.this.view); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java index bf7844a0f4..0eb3c3b21e 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java @@ -17,6 +17,7 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; +import java.util.ArrayList; import java.util.List; import static net.osmand.util.Algorithms.capitalizeFirstLetter; @@ -24,19 +25,28 @@ import static net.osmand.util.Algorithms.capitalizeFirstLetter; public class HorizontalSelectionAdapter extends RecyclerView.Adapter { - private List items; + public static int INVALID_ID = -1; + + private List items; private OsmandApplication app; private boolean nightMode; private HorizontalSelectionAdapterListener listener; - - private String selectedItem = ""; + private HorizontalSelectionItem selectedItem = null; public HorizontalSelectionAdapter(OsmandApplication app, boolean nightMode) { this.app = app; this.nightMode = nightMode; } - public void setItems(List items) { + public void setTitledItems(List titles) { + List items = new ArrayList<>(); + for (String title : titles) { + items.add(new HorizontalSelectionItem(title)); + } + setItems(items); + } + + public void setItems(List items) { this.items = items; } @@ -44,23 +54,30 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter getAllRouteSegments() { + class TmpRouteSegmentData { + private WptPt start; + private WptPt end; + private List routeSegments; + + public TmpRouteSegmentData(WptPt start, WptPt end, + List routeSegments) { + this.start = start; + this.end = end; + this.routeSegments = new ArrayList<>(routeSegments); + } + + boolean isAfterOf(TmpRouteSegmentData other) { + return Algorithms.objectEquals(this.start, other.end); + } + + boolean isBeforeOf(TmpRouteSegmentData other) { + return Algorithms.objectEquals(this.end, other.start); + } + + void joinAfter(TmpRouteSegmentData other) { + end = other.end; + routeSegments.addAll(other.routeSegments); + } + + void joinBefore(TmpRouteSegmentData other) { + start = other.start; + routeSegments.addAll(0, other.routeSegments); + } + } + + // prepare data for sorting + List fullList = new ArrayList<>(); + for (Map.Entry, RoadSegmentData> entry : roadSegmentData.entrySet()) { + fullList.add(new TmpRouteSegmentData( + entry.getKey().first, + entry.getKey().second, + entry.getValue().getSegments())); + } + + // sorting data by connecting together + while (fullList.size() > 1) { + TmpRouteSegmentData firstInList = fullList.get(0); + for (int i = 1; i < fullList.size(); i++) { + TmpRouteSegmentData other = fullList.get(i); + boolean isMatched = false; + + if (firstInList.isAfterOf(other)) { + isMatched = true; + firstInList.joinBefore(other); + } else if (firstInList.isBeforeOf(other)) { + isMatched = true; + firstInList.joinAfter(other); + } + + if (isMatched) { + fullList.remove(other); + break; + } + } + } + + return fullList.size() > 0 ? fullList.get(0).routeSegments : null; + } + void splitSegments(int position) { List points = new ArrayList<>(); points.addAll(before.points); diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index 0cb1ae2f33..d5c21d8c07 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; @@ -14,6 +13,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -26,8 +26,8 @@ import androidx.core.content.ContextCompat; import androidx.core.widget.TextViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; @@ -62,7 +62,6 @@ import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragme import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragment.RouteBetweenPointsFragmentListener; import net.osmand.plus.measurementtool.SaveGpxRouteAsyncTask.SaveGpxRouteListener; import net.osmand.plus.measurementtool.SelectedPointBottomSheetDialogFragment.SelectedPointFragmentListener; -import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter.MeasurementAdapterListener; import net.osmand.plus.measurementtool.command.AddPointCommand; import net.osmand.plus.measurementtool.command.ApplyGpxApproximationCommand; @@ -75,7 +74,6 @@ import net.osmand.plus.measurementtool.command.ReorderPointCommand; import net.osmand.plus.measurementtool.command.ReversePointsCommand; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; import net.osmand.plus.views.layers.MapControlsLayer; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType; @@ -92,6 +90,8 @@ import java.util.Locale; import static net.osmand.IndexConstants.GPX_FILE_EXT; import static net.osmand.IndexConstants.GPX_INDEX_DIR; +import static net.osmand.plus.UiUtilities.CustomRadioButtonType.END; +import static net.osmand.plus.UiUtilities.CustomRadioButtonType.START; import static net.osmand.plus.measurementtool.MeasurementEditingContext.CalculationMode; import static net.osmand.plus.measurementtool.MeasurementEditingContext.SnapToRoadProgressListener; import static net.osmand.plus.measurementtool.SaveAsNewTrackBottomSheetDialogFragment.SaveAsNewTrackFragmentListener; @@ -109,18 +109,14 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public static final String TAG = MeasurementToolFragment.class.getSimpleName(); public static final String TAPS_DISABLED_KEY = "taps_disabled_key"; - private RecyclerView pointsRv; private String previousToolBarTitle = ""; private MeasurementToolBarController toolBarController; - private MeasurementToolAdapter adapter; private TextView distanceTv; private TextView pointsTv; private TextView distanceToCenterTv; private String pointsSt; - private Drawable upIcon; - private Drawable downIcon; - private View pointsListContainer; - private View upDownRow; + private View additionalInfoContainer; + private LinearLayout customRadioButton; private View mainView; private ImageView upDownBtn; private ImageView undoBtn; @@ -131,7 +127,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private boolean wasCollapseButtonVisible; private boolean progressBarVisible; - private boolean pointsListOpened; + private boolean additionalInfoExpanded; private static final int PLAN_ROUTE_MODE = 0x1; private static final int DIRECTION_MODE = 0x2; @@ -143,6 +139,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private boolean portrait; private boolean nightMode; private int cachedMapPosition; + private AdditionalInfoType currentAdditionalInfoType; private MeasurementEditingContext editingCtx = new MeasurementEditingContext(); @@ -159,8 +156,15 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route SHOW_IS_SAVED_FRAGMENT } - protected MeasurementEditingContext getEditingCtx() { - return editingCtx; + private enum AdditionalInfoType { + POINTS(MtPointsFragment.TAG), + GRAPH(MtGraphFragment.TAG); + + AdditionalInfoType(String fragmentName) { + this.fragmentName = fragmentName; + } + + String fragmentName; } private void setEditingCtx(MeasurementEditingContext editingCtx) { @@ -244,17 +248,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route measurementLayer.setEditingCtx(editingCtx); - // If rotate the screen from landscape to portrait when the list of points is displayed then - // the RecyclerViewFragment will exist without view. This is necessary to remove it. - if (!portrait) { - hidePointsListFragment(); - } - nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls(); portrait = AndroidUiHelper.isOrientationPortrait(mapActivity); - upIcon = getContentIcon(R.drawable.ic_action_arrow_up); - downIcon = getContentIcon(R.drawable.ic_action_arrow_down); pointsSt = getString(R.string.shared_string_gpx_points).toLowerCase(); View view = UiUtilities.getInflater(getContext(), nightMode) @@ -262,12 +258,29 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route mainView = view.findViewById(R.id.main_view); AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark); - pointsListContainer = view.findViewById(R.id.points_list_container); - if (portrait && pointsListContainer != null) { - final int backgroundColor = ContextCompat.getColor(mapActivity, nightMode - ? R.color.activity_background_color_dark - : R.color.activity_background_color_light); - pointsListContainer.setBackgroundColor(backgroundColor); + additionalInfoContainer = mainView.findViewById(R.id.additional_info_container); + if (portrait) { + customRadioButton = mainView.findViewById(R.id.custom_radio_buttons); + + View pointListBtn = customRadioButton.findViewById(R.id.left_button_container); + TextView tvPointListBtn = customRadioButton.findViewById(R.id.left_button); + tvPointListBtn.setText(R.string.shared_string_gpx_points); + pointListBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + changeAdditionalInfoType(AdditionalInfoType.POINTS); + } + }); + + View graphBtn = customRadioButton.findViewById(R.id.right_button_container); + TextView tvGraphBtn = customRadioButton.findViewById(R.id.right_button); + tvGraphBtn.setText(R.string.shared_string_graph); + graphBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + changeAdditionalInfoType(AdditionalInfoType.GRAPH); + } + }); } if (progressBarVisible) { @@ -280,7 +293,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route mainIcon = (ImageView) mainView.findViewById(R.id.main_icon); upDownBtn = (ImageView) mainView.findViewById(R.id.up_down_button); - upDownBtn.setImageDrawable(upIcon); + updateUpDownBtn(); mainView.findViewById(R.id.cancel_move_point_button).setOnClickListener(new OnClickListener() { @Override @@ -296,14 +309,14 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } }); - upDownRow = mainView.findViewById(R.id.up_down_row); + View upDownRow = mainView.findViewById(R.id.up_down_row); upDownRow.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - if (!pointsListOpened && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) { - showPointsList(); + if (!additionalInfoExpanded && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) { + expandAdditionalInfoView(); } else { - hidePointsList(); + collapseAdditionalInfoView(); } } }); @@ -396,8 +409,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void onSelectPoint(int selectedPointPos) { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } if (selectedPointPos != -1) { openSelectedPointMenu(mapActivity); @@ -420,8 +433,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route measurementLayer.setOnEnterMovePointModeListener(new MeasurementToolLayer.OnEnterMovePointModeListener() { @Override public void onEnterMovePointMode() { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } switchMovePointMode(true); } @@ -474,18 +487,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route updateToolbar(); final GpxData gpxData = editingCtx.getGpxData(); - adapter = new MeasurementToolAdapter(getMapActivity(), editingCtx.getPoints(), - gpxData != null ? gpxData.getActionType() : null); - if (portrait) { - pointsRv = mainView.findViewById(R.id.measure_points_recycler_view); - } else { - pointsRv = new RecyclerView(getActivity()); - } - ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); - touchHelper.attachToRecyclerView(pointsRv); - adapter.setAdapterListener(createMeasurementAdapterListener(touchHelper)); - pointsRv.setLayoutManager(new LinearLayoutManager(getContext())); - pointsRv.setAdapter(adapter); ImageButton snapToRoadBtn = (ImageButton) mapActivity.findViewById(R.id.snap_to_road_image_button); snapToRoadBtn.setBackgroundResource(nightMode ? R.drawable.btn_circle_night : R.drawable.btn_circle); @@ -512,6 +513,30 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route return view; } + private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) { + if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) { + currentAdditionalInfoType = type; + additionalInfoExpanded = true; + updateUpDownBtn(); + OsmandApplication app = getMyApplication(); + if (AdditionalInfoType.POINTS.equals(type)) { + UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START); + } else if (AdditionalInfoType.GRAPH.equals(type)) { + UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END); + } else { + return; + } + setAdditionalInfoFragment(type.fragmentName); + } + } + + private void updateAdditionalInfoView() { + Fragment fragment = getActiveAdditionalInfoFragment(); + if (fragment instanceof OnUpdateAdditionalInfoListener) { + ((OnUpdateAdditionalInfoListener) fragment).onUpdateAdditionalInfo(); + } + } + public boolean isInEditMode() { return !isPlanRouteMode() && !editingCtx.isNewData() && !isDirectionMode() && !isFollowTrackMode(); } @@ -520,12 +545,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route this.fileName = fileName; } + public MeasurementEditingContext getEditingCtx() { + return editingCtx; + } + private void updateUndoRedoCommonStuff() { - hidePointsListIfNoPoints(); + collapseAdditionalInfoIfNoPointsEnough(); if (editingCtx.getPointsCount() > 0) { enable(upDownBtn); } - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateDistancePointsText(); updateSnapToRoadControls(); } @@ -585,9 +614,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route super.onDestroyView(); cancelModes(); exitMeasurementMode(); - adapter.setAdapterListener(null); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } MeasurementToolLayer layer = getMeasurementLayer(); if (layer != null) { @@ -834,8 +862,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, ALL)); editingCtx.cancelSnapToRoad(); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateUndoRedoButton(false, redoBtn); disable(upDownBtn); @@ -850,8 +878,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (points.size() > 1) { MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ReversePointsCommand(measurementLayer)); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateUndoRedoButton(false, redoBtn); updateUndoRedoButton(true, undoBtn); @@ -919,8 +947,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void trimRoute(ClearCommandMode before) { MeasurementToolLayer measurementLayer = getMeasurementLayer(); editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, before)); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } editingCtx.setSelectedPointPosition(-1); editingCtx.splitSegments(editingCtx.getBeforePoints().size() + editingCtx.getAfterPoints().size()); @@ -1077,11 +1105,11 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void removePoint(MeasurementToolLayer measurementLayer, int position) { if (measurementLayer != null) { editingCtx.getCommandManager().execute(new RemovePointCommand(measurementLayer, position)); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateUndoRedoButton(true, undoBtn); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); - hidePointsListIfNoPoints(); + collapseAdditionalInfoIfNoPointsEnough(); } } @@ -1104,7 +1132,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } } - private MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) { + MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) { return new MeasurementAdapterListener() { final MapActivity mapActivity = getMapActivity(); @@ -1120,8 +1148,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void onItemClick(int position) { if (mapActivity != null && measurementLayer != null) { - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } if (portrait) { setMapPosition(OsmandSettings.MIDDLE_TOP_CONSTANT); @@ -1143,7 +1171,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route toPosition = holder.getAdapterPosition(); if (toPosition >= 0 && fromPosition >= 0 && toPosition != fromPosition) { editingCtx.getCommandManager().execute(new ReorderPointCommand(measurementLayer, fromPosition, toPosition)); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); mapActivity.refreshMap(); @@ -1209,7 +1237,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (!isUndoMode()) { editingCtx.addPoints(points); } - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateDistancePointsText(); } } @@ -1221,7 +1249,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (!isUndoMode()) { editingCtx.addPoints(); } - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); updateDistancePointsText(); } } @@ -1417,85 +1445,91 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route updateUndoRedoButton(true, undoBtn); updateUndoRedoButton(false, redoBtn); updateDistancePointsText(); - adapter.notifyDataSetChanged(); + updateAdditionalInfoView(); } - private void showPointsList() { - pointsListOpened = true; - upDownBtn.setImageDrawable(downIcon); - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - if (portrait && pointsListContainer != null) { - pointsListContainer.setVisibility(View.VISIBLE); - } else { - showPointsListFragment(); + private void expandAdditionalInfoView() { + if (portrait) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + additionalInfoContainer.setVisibility(View.VISIBLE); + AdditionalInfoType typeToShow = currentAdditionalInfoType == null + ? AdditionalInfoType.POINTS : currentAdditionalInfoType; + changeAdditionalInfoType(typeToShow); + setMapPosition(portrait + ? OsmandSettings.MIDDLE_TOP_CONSTANT + : OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT); } - setMapPosition(portrait - ? OsmandSettings.MIDDLE_TOP_CONSTANT - : OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT); } } - private void hidePointsList() { - pointsListOpened = false; - upDownBtn.setImageDrawable(upIcon); - if (portrait && pointsListContainer != null) { - pointsListContainer.setVisibility(View.GONE); - } else { - hidePointsListFragment(); + private void collapseAdditionalInfoView() { + if (portrait) { + additionalInfoExpanded = false; + updateUpDownBtn(); + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + Fragment activeFragment = getActiveAdditionalInfoFragment(); + if (activeFragment != null) { + FragmentManager manager = getChildFragmentManager(); + manager.beginTransaction().remove(activeFragment).commitAllowingStateLoss(); + } + additionalInfoContainer.setVisibility(View.GONE); + setDefaultMapPosition(); + } } - setDefaultMapPosition(); } - private void hidePointsListIfNoPoints() { + private void setAdditionalInfoFragment(String fragmentName) { + Context ctx = getContext(); + if (ctx == null) return; + + Fragment fragment = Fragment.instantiate(ctx, fragmentName); + FragmentManager fm = getChildFragmentManager(); + FragmentTransaction fragmentTransaction = fm.beginTransaction(); + fragmentTransaction.replace(R.id.fragmentContainer, fragment, fragmentName); + fragmentTransaction.commit(); + fm.executePendingTransactions(); + } + + private void collapseAdditionalInfoIfNoPointsEnough() { MeasurementToolLayer measurementLayer = getMeasurementLayer(); if (measurementLayer != null) { - if (editingCtx.getPointsCount() < 1) { + int pointsCount = editingCtx.getPointsCount(); + if (isCurrentAdditionalInfoType(AdditionalInfoType.GRAPH) && pointsCount < 2) { + collapseAdditionalInfoView(); + } else if (pointsCount < 1) { disable(upDownBtn); - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } } } } - private void showPointsListFragment() { + private Fragment getActiveAdditionalInfoFragment() { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { - boolean transparentStatusBar = Build.VERSION.SDK_INT >= 21; - int statusBarHeight = transparentStatusBar ? 0 : AndroidUtils.getStatusBarHeight(mapActivity); - int screenHeight = AndroidUtils.getScreenHeight(mapActivity) - statusBarHeight; - RecyclerViewFragment fragment = new RecyclerViewFragment(); - fragment.setRecyclerView(pointsRv); - fragment.setWidth(upDownRow.getWidth()); - fragment.setHeight(screenHeight - upDownRow.getHeight()); - fragment.setTransparentStatusBar(transparentStatusBar); - mapActivity.getSupportFragmentManager().beginTransaction() - .add(R.id.fragmentContainer, fragment, RecyclerViewFragment.TAG) - .commitAllowingStateLoss(); - } - } - - private void hidePointsListFragment() { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - try { - FragmentManager manager = mapActivity.getSupportFragmentManager(); - Fragment fragment = manager.findFragmentByTag(RecyclerViewFragment.TAG); - if (fragment != null) { - manager.beginTransaction().remove(fragment).commitAllowingStateLoss(); + for (AdditionalInfoType type : AdditionalInfoType.values()) { + try { + FragmentManager fm = getChildFragmentManager(); + Fragment fragment = fm.findFragmentByTag(type.fragmentName); + if (fragment != null) { + return fragment; + } + } catch (Exception e) { + // ignore } - } catch (Exception e) { - // ignore } } + return null; } private void setDefaultMapPosition() { setMapPosition(OsmandSettings.CENTER_CONSTANT); } - private void setMapPosition(int position) { + public void setMapPosition(int position) { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { mapActivity.getMapView().setMapPosition(position); @@ -1515,6 +1549,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } } + private void updateUpDownBtn() { + Drawable icon = getContentIcon(additionalInfoExpanded + ? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up); + upDownBtn.setImageDrawable(icon); + } + + private boolean isCurrentAdditionalInfoType(@NonNull AdditionalInfoType type) { + return type.equals(currentAdditionalInfoType); + } + private String getSuggestedFileName() { GpxData gpxData = editingCtx.getGpxData(); String displayedName; @@ -1790,8 +1834,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route final MapActivity mapActivity = getMapActivity(); MeasurementToolLayer measurementLayer = getMeasurementLayer(); if (mapActivity != null && measurementLayer != null) { - if (pointsListOpened && hidePointsListFirst) { - hidePointsList(); + if (additionalInfoExpanded && hidePointsListFirst) { + collapseAdditionalInfoView(); return; } if (!editingCtx.hasChanges()) { @@ -1811,8 +1855,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (clearContext) { editingCtx.clearSegments(); } - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } resetAppMode(); hideSnapToRoadIcon(); @@ -1949,8 +1993,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (!editingCtx.getCommandManager().update(command)) { editingCtx.getCommandManager().execute(command); } - if (pointsListOpened) { - hidePointsList(); + if (additionalInfoExpanded) { + collapseAdditionalInfoView(); } updateSnapToRoadControls(); } @@ -2024,4 +2068,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public boolean isNightModeForMapControls() { return nightMode; } + + public interface OnUpdateAdditionalInfoListener { + void onUpdateAdditionalInfo(); + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java new file mode 100644 index 0000000000..7b35b6dce1 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -0,0 +1,414 @@ +package net.osmand.plus.measurementtool; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.github.mikephil.charting.charts.HorizontalBarChart; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.SettingsBaseActivity; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; +import net.osmand.plus.render.MapRenderRepositories; +import net.osmand.render.RenderingRuleSearchRequest; +import net.osmand.render.RenderingRulesStorage; +import net.osmand.router.RouteSegmentResult; +import net.osmand.router.RouteStatisticsHelper; +import net.osmand.util.Algorithms; + +import static net.osmand.router.RouteStatisticsHelper.RouteStatistics; +import static net.osmand.GPXUtilities.GPXTrackAnalysis; +import static net.osmand.GPXUtilities.GPXFile; +import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MtGraphFragment extends Fragment + implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { + + public static final String TAG = MtGraphFragment.class.getName(); + + private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp"; + + private View commonGraphContainer; + private View customGraphContainer; + private View messageContainer; + private LineChart commonGraphChart; + private HorizontalBarChart customGraphChart; + private RecyclerView rvMenu; + + private boolean nightMode; + private MeasurementEditingContext editingCtx; + private GraphType currentGraphType; + private Map graphData = new HashMap<>(); + + private enum GraphType { + OVERVIEW(R.string.shared_string_overview, false, false), + ALTITUDE(R.string.altitude, false, true), +// SLOPE(R.string.shared_string_slope, false, true), + SPEED(R.string.map_widget_speed, false, false), + + SURFACE(R.string.routeInfo_surface_name, true, false), + ROAD_TYPE(R.string.routeInfo_roadClass_name, true, false), + STEEPNESS(R.string.routeInfo_steepness_name, true, false), + SMOOTHNESS(R.string.routeInfo_smoothness_name, true, false); + + GraphType(int titleId, boolean isCustomType, boolean canBeCalculated) { + this.titleId = titleId; + this.isCustomType = isCustomType; + this.canBeCalculated = canBeCalculated; + } + + final int titleId; + final boolean isCustomType; + final boolean canBeCalculated; + + private static List commonTypes; + private static List customTypes; + + static List getCommonTypes() { + if (commonTypes == null) { + prepareLists(); + } + return commonTypes; + } + + static List getCustomTypes() { + if (customTypes == null) { + prepareLists(); + } + return customTypes; + } + + private static void prepareLists() { + commonTypes = new ArrayList<>(); + customTypes = new ArrayList<>(); + for (GraphType type : values()) { + if (type.isCustomType) { + customTypes.add(type); + } else { + commonTypes.add(type); + } + } + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + final MapActivity mapActivity = (MapActivity) getActivity(); + final MeasurementToolFragment mtf = (MeasurementToolFragment) getParentFragment(); + if (mapActivity == null || mtf == null) return null; + + editingCtx = mtf.getEditingCtx(); + OsmandApplication app = mapActivity.getMyApplication(); + + nightMode = app.getDaynightHelper().isNightModeForMapControls(); + View view = UiUtilities.getInflater(app, nightMode).inflate( + R.layout.fragment_measurement_tool_graph, container, false); + commonGraphContainer = view.findViewById(R.id.common_graphs_container); + customGraphContainer = view.findViewById(R.id.custom_graphs_container); + messageContainer = view.findViewById(R.id.message_container); + commonGraphChart = (LineChart) view.findViewById(R.id.line_chart); + customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); + updateGraphData(); + + rvMenu = view.findViewById(R.id.graph_types_recycler_view); + rvMenu.setLayoutManager( + new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); + + prepareGraphTypesSelectionMenu(); + setupVisibleGraphType(GraphType.OVERVIEW); + + return view; + } + + private void prepareGraphTypesSelectionMenu() { + rvMenu.removeAllViews(); + OsmandApplication app = getMyApplication(); + int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; + final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode); + final ArrayList items = new ArrayList<>(); + for (GraphType type : GraphType.values()) { + String title = getString(type.titleId); + HorizontalSelectionItem item = new HorizontalSelectionItem(title, type); + if (type.isCustomType) { + item.setTitleColorId(activeColorId); + } + if (isDataAvailableFor(type) || type.canBeCalculated) { + items.add(item); + } + } + adapter.setItems(items); + String selectedItemKey = currentGraphType != null ? + getString(currentGraphType.titleId) : items.get(0).getTitle(); + adapter.setSelectedItemByTitle(selectedItemKey); + adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { + @Override + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + adapter.setItems(items); + adapter.setSelectedItem(item); + GraphType chosenGraphType = (GraphType) item.getObject(); + if (chosenGraphType != null && !chosenGraphType.equals(currentGraphType)) { + setupVisibleGraphType(chosenGraphType); + } + } + }); + rvMenu.setAdapter(adapter); + adapter.notifyDataSetChanged(); + } + + @Override + public void onUpdateAdditionalInfo() { + updateGraphData(); + prepareGraphTypesSelectionMenu(); + setupVisibleGraphType(currentGraphType); + } + + private void setupVisibleGraphType(GraphType preferredType) { + currentGraphType = isDataAvailableFor(preferredType) ? + preferredType : getFirstAvailableGraphType(); + updateDataView(); + } + + private GraphType getFirstAvailableGraphType() { + for (GraphType type : GraphType.values()) { + if (isDataAvailableFor(type) || type.canBeCalculated) { + return type; + } + } + return GraphType.OVERVIEW; + } + + private void updateDataView() { + if (isDataAvailableFor(currentGraphType)) { + showGraph(); + } else if (currentGraphType.canBeCalculated) { + showMessage(); + } + } + + private void showGraph() { + if (currentGraphType.isCustomType) { + customGraphChart.clear(); + commonGraphContainer.setVisibility(View.GONE); + customGraphContainer.setVisibility(View.VISIBLE); + messageContainer.setVisibility(View.GONE); + prepareCustomGraphView(); + } else { + commonGraphChart.clear(); + commonGraphContainer.setVisibility(View.VISIBLE); + customGraphContainer.setVisibility(View.GONE); + messageContainer.setVisibility(View.GONE); + prepareCommonGraphView(); + } + } + + private void showMessage() { + commonGraphContainer.setVisibility(View.GONE); + customGraphContainer.setVisibility(View.GONE); + messageContainer.setVisibility(View.VISIBLE); + TextView tvMessage = messageContainer.findViewById(R.id.message_text); + ImageView icon = messageContainer.findViewById(R.id.message_icon); + if (GraphType.ALTITUDE.equals(currentGraphType)) { + tvMessage.setText(R.string.message_need_calculate_route_for_show_graph); + icon.setImageResource(R.drawable.ic_action_altitude_average); + } + } + + private void prepareCommonGraphView() { + LineData data = (LineData) graphData.get(currentGraphType); + if (data == null) return; + + GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true); + commonGraphChart.setData(data); + } + + private void prepareCustomGraphView() { + BarData data = (BarData) graphData.get(currentGraphType); + OsmandApplication app = getMapActivity().getMyApplication(); + if (data == null || app == null) return; + + GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode); + customGraphChart.setExtraRightOffset(16); + customGraphChart.setExtraLeftOffset(16); + customGraphChart.setData(data); + } + + private void updateGraphData() { + OsmandApplication app = getMyApplication(); + GPXTrackAnalysis analysis = createGpxTrackAnalysis(); + + // update common graph data + for (GraphType type : GraphType.getCommonTypes()) { + List dataSets = getDataSets(type, commonGraphChart, analysis); + if (!Algorithms.isEmpty(dataSets)) { + graphData.put(type, new LineData(dataSets)); + } else { + graphData.put(type, null); + } + } + + // update custom graph data + List routeSegments = editingCtx.getAllRouteSegments(); + List routeStatistics = calculateRouteStatistics(routeSegments); + for (GraphType type : GraphType.getCustomTypes()) { + RouteStatistics statistic = getStatisticForGraphType(routeStatistics, type); + if (statistic != null && !Algorithms.isEmpty(statistic.elements)) { + BarData data = GpxUiHelper.buildStatisticChart( + app, customGraphChart, statistic, analysis, true, nightMode); + graphData.put(type, data); + } else { + graphData.put(type, null); + } + } + } + + private RouteStatistics getStatisticForGraphType(List routeStatistics, GraphType graphType) { + if (routeStatistics == null) return null; + for (RouteStatistics statistic : routeStatistics) { + int graphTypeId = graphType.titleId; + int statisticId = SettingsBaseActivity.getStringRouteInfoPropertyValueId(statistic.name); + if (graphTypeId == statisticId) { + return statistic; + } + } + return null; + } + + private List getDataSets(GraphType graphType, LineChart chart, GPXTrackAnalysis analysis) { + List dataSets = new ArrayList<>(); + if (chart != null && analysis != null) { + OsmandApplication app = getMyApplication(); + switch (graphType) { + case OVERVIEW: { + GpxUiHelper.OrderedLineDataSet speedDataSet = null; + GpxUiHelper.OrderedLineDataSet elevationDataSet = null; +// GpxUiHelper.OrderedLineDataSet slopeDataSet = null; + if (analysis.hasSpeedData) { + speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false); + } + if (analysis.hasElevationData) { + elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false); +// slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, +// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); + } + List dataList = new ArrayList<>(); + if (speedDataSet != null) { + dataList.add(speedDataSet); + } + if (elevationDataSet != null) { + dataList.add(elevationDataSet); + } +// if (slopeDataSet != null) { +// dataList.add(slopeDataSet); +// } + if (dataList.size() > 0) { + Collections.sort(dataList, new Comparator() { + @Override + public int compare(GpxUiHelper.OrderedLineDataSet o1, GpxUiHelper.OrderedLineDataSet o2) { + return Float.compare(o1.getPriority(), o2.getPriority()); + } + }); + } + dataSets.addAll(dataList); + break; + } + case ALTITUDE: { + if (analysis.hasElevationData) { + GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); + if (elevationDataSet != null) { + dataSets.add(elevationDataSet); + } + } + break; + } +// case SLOPE: +// if (analysis.hasElevationData) { +// GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, +// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); +// if (slopeDataSet != null) { +// dataSets.add(slopeDataSet); +// } +// } +// break; + case SPEED: { + if (analysis.hasSpeedData) { + GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); + if (speedDataSet != null) { + dataSets.add(speedDataSet); + } + } + break; + } + } + } + return dataSets; + } + + private GPXTrackAnalysis createGpxTrackAnalysis() { + GPXFile gpx; + if (editingCtx.getGpxData() != null) { + gpx = editingCtx.getGpxData().getGpxFile(); + } else { + gpx = editingCtx.exportRouteAsGpx(GRAPH_DATA_GPX_FILE_NAME); + } + return gpx != null ? gpx.getAnalysis(0) : null; + } + + private List calculateRouteStatistics(List route) { + OsmandApplication app = getMyApplication(); + if (route == null || app == null) return null; + + RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); + RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); + MapRenderRepositories maps = app.getResourceManager().getRenderer(); + RenderingRuleSearchRequest currentSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(currentRenderer, nightMode); + RenderingRuleSearchRequest defaultSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(defaultRender, nightMode); + return RouteStatisticsHelper.calculateRouteStatistic(route, currentRenderer, + defaultRender, currentSearchRequest, defaultSearchRequest); + } + + private boolean isDataAvailableFor(GraphType graphType) { + return graphData != null && graphData.get(graphType) != null; + } + + private OsmandApplication getMyApplication() { + return getMapActivity().getMyApplication(); + } + + private MapActivity getMapActivity() { + return (MapActivity) getActivity(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java new file mode 100644 index 0000000000..9ee46b3648 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java @@ -0,0 +1,70 @@ +package net.osmand.plus.measurementtool; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; +import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; + +public class MtPointsFragment extends Fragment + implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { + + public static final String TAG = MtPointsFragment.class.getName(); + + private boolean nightMode; + private MeasurementToolAdapter adapter; + private MeasurementEditingContext editingCtx; + private RecyclerView pointsRv; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + final MapActivity mapActivity = (MapActivity) getActivity(); + final MeasurementToolFragment mtf = (MeasurementToolFragment) getParentFragment(); + if (mapActivity == null || mtf == null) { + return null; + } + nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls(); + View view = UiUtilities.getInflater(getContext(), nightMode) + .inflate(R.layout.fragment_measurement_tool_points_list, container, false); + + editingCtx = mtf.getEditingCtx(); + final GpxData gpxData = editingCtx.getGpxData(); + adapter = new MeasurementToolAdapter(mapActivity, editingCtx.getPoints(), + gpxData != null ? gpxData.getActionType() : null); + pointsRv = view.findViewById(R.id.measure_points_recycler_view); + ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); + touchHelper.attachToRecyclerView(pointsRv); + adapter.setAdapterListener(mtf.createMeasurementAdapterListener(touchHelper)); + pointsRv.setLayoutManager(new LinearLayoutManager(getContext())); + pointsRv.setAdapter(adapter); + + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + adapter.setAdapterListener(null); + } + + @Override + public void onUpdateAdditionalInfo() { + adapter.notifyDataSetChanged(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java b/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java index c155ec493c..b89cbda954 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/SelectFileBottomSheet.java @@ -28,6 +28,7 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; import net.osmand.plus.helpers.enums.TracksSortByMode; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener; +import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; import java.io.File; import java.util.ArrayList; @@ -128,7 +129,7 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment { sortButton.setImageResource(mode.getIconId()); updateDescription(descriptionView); sortFolderList(); - folderAdapter.setItems(getFolderNames()); + folderAdapter.setTitledItems(getFolderNames()); folderAdapter.notifyDataSetChanged(); sortFileList(); adapter.notifyDataSetChanged(); @@ -191,13 +192,13 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment { folders = new ArrayList<>(); collectDirs(gpxDir, folders); sortFolderList(); - folderAdapter.setItems(getFolderNames()); - folderAdapter.setSelectedItem(selectedFolder); + folderAdapter.setTitledItems(getFolderNames()); + folderAdapter.setSelectedItemByTitle(selectedFolder); foldersRecyclerView.setAdapter(folderAdapter); folderAdapter.setListener(new HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedFolder = item; + public void onItemSelected(HorizontalSelectionItem item) { + selectedFolder = item.getTitle(); updateFileList(folderAdapter); } }); diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java index 94770e0135..c21b508116 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/cards/TracksToFollowCard.java @@ -85,13 +85,13 @@ public class TracksToFollowCard extends BaseCard { private void setupCategoriesRow() { final HorizontalSelectionAdapter selectionAdapter = new HorizontalSelectionAdapter(app, nightMode); - selectionAdapter.setItems(new ArrayList<>(gpxInfoCategories.keySet())); - selectionAdapter.setSelectedItem(selectedCategory); + selectionAdapter.setTitledItems(new ArrayList<>(gpxInfoCategories.keySet())); + selectionAdapter.setSelectedItemByTitle(selectedCategory); selectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedCategory = item; - List items = gpxInfoCategories.get(item); + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedCategory = item.getTitle(); + List items = gpxInfoCategories.get(selectedCategory); tracksAdapter.setShowFolderName(showFoldersName()); tracksAdapter.setGpxInfoList(items != null ? items : new ArrayList()); tracksAdapter.notifyDataSetChanged(); diff --git a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java index 5ac7aea8e8..9d3275e8fd 100644 --- a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/VehicleParametersBottomSheet.java @@ -121,7 +121,7 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet { currentValue = 0.0f; } selectedItem = preference.getEntryFromValue(String.valueOf(currentValue)); - adapter.setSelectedItem(selectedItem); + adapter.setSelectedItemByTitle(selectedItem); int itemPosition = adapter.getItemPosition(selectedItem); if (itemPosition >= 0) { recyclerView.smoothScrollToPosition(itemPosition); @@ -129,11 +129,11 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet { } }); - adapter.setItems(Arrays.asList(preference.getEntries())); + adapter.setTitledItems(Arrays.asList(preference.getEntries())); adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override - public void onItemSelected(String item) { - selectedItem = item; + public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) { + selectedItem = item.getTitle(); currentValue = preference.getValueFromEntries(selectedItem); String currentValueStr = currentValue == 0.0f ? "" : df.format(currentValue + 0.01f); @@ -145,7 +145,7 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet { } }); recyclerView.setAdapter(adapter); - adapter.setSelectedItem(selectedItem); + adapter.setSelectedItemByTitle(selectedItem); return new BaseBottomSheetItem.Builder() .setCustomView(mainView) .create(); From 3c2bda87ce9ef0ee60a4a24dadb5aec5b9830d24 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Mon, 12 Oct 2020 10:33:46 +0300 Subject: [PATCH 003/123] small refactoring --- .../measurementtool/MeasurementToolFragment.java | 15 ++++++++++----- .../plus/measurementtool/MtGraphFragment.java | 2 -- .../plus/measurementtool/MtPointsFragment.java | 2 -- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index d5c21d8c07..a927ca884e 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -157,14 +157,18 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } private enum AdditionalInfoType { - POINTS(MtPointsFragment.TAG), - GRAPH(MtGraphFragment.TAG); + POINTS(MtPointsFragment.class.getName()), + GRAPH(MtGraphFragment.class.getName()); AdditionalInfoType(String fragmentName) { this.fragmentName = fragmentName; } - String fragmentName; + final String fragmentName; + + public String getFragmentName() { + return fragmentName; + } } private void setEditingCtx(MeasurementEditingContext editingCtx) { @@ -237,6 +241,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route public void hideProgressBar() { ((ProgressBar) mainView.findViewById(R.id.snap_to_road_progress_bar)).setVisibility(View.GONE); progressBarVisible = false; + updateAdditionalInfoView(); } @Override @@ -526,7 +531,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } else { return; } - setAdditionalInfoFragment(type.fragmentName); + setAdditionalInfoFragment(type.getFragmentName()); } } @@ -1513,7 +1518,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route for (AdditionalInfoType type : AdditionalInfoType.values()) { try { FragmentManager fm = getChildFragmentManager(); - Fragment fragment = fm.findFragmentByTag(type.fragmentName); + Fragment fragment = fm.findFragmentByTag(type.getFragmentName()); if (fragment != null) { return fragment; } diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index 7b35b6dce1..72cc761c84 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -48,8 +48,6 @@ import java.util.Map; public class MtGraphFragment extends Fragment implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { - public static final String TAG = MtGraphFragment.class.getName(); - private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp"; private View commonGraphContainer; diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java index 9ee46b3648..086ab6c322 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java @@ -21,8 +21,6 @@ import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; public class MtPointsFragment extends Fragment implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { - public static final String TAG = MtPointsFragment.class.getName(); - private boolean nightMode; private MeasurementToolAdapter adapter; private MeasurementEditingContext editingCtx; From 65de824b557c0eb1092fee240df9ac64d7f86052 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 12 Oct 2020 13:29:21 +0300 Subject: [PATCH 004/123] Use SettingsScreenType in plugins --- .../osmand/access/AccessibilityPlugin.java | 8 ++++---- OsmAnd/src/net/osmand/plus/OsmandPlugin.java | 5 ++--- .../audionotes/AudioVideoNotesPlugin.java | 6 +++--- .../development/OsmandDevelopmentPlugin.java | 6 +++--- .../monitoring/OsmandMonitoringPlugin.java | 6 +++--- .../osmand/plus/osmedit/OsmEditingPlugin.java | 19 ++++++++++++------- .../fragments/BaseSettingsFragment.java | 10 +++++----- .../fragments/ConfigureProfileFragment.java | 4 ++-- 8 files changed, 34 insertions(+), 30 deletions(-) diff --git a/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java b/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java index b9ea4a62cf..ef6895363a 100644 --- a/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java +++ b/OsmAnd/src/net/osmand/access/AccessibilityPlugin.java @@ -8,9 +8,9 @@ import androidx.annotation.NonNull; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import java.io.IOException; import java.util.HashMap; @@ -71,8 +71,8 @@ public class AccessibilityPlugin extends OsmandPlugin { } @Override - public Class getSettingsFragment() { - return AccessibilitySettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.ACCESSIBILITY_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java index 4e9ca3d420..8b3326cf02 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java @@ -46,10 +46,9 @@ import net.osmand.plus.quickaction.QuickActionType; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandPreference; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.skimapsplugin.SkiMapsPlugin; import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.views.OsmandMapTileView; @@ -116,7 +115,7 @@ public abstract class OsmandPlugin { return null; } - public Class getSettingsFragment() { + public SettingsScreenType getSettingsScreenType() { return null; } diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java index a68a8bc749..fe023e9dd7 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java @@ -66,7 +66,7 @@ import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.quickaction.QuickActionType; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; @@ -1805,8 +1805,8 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } @Override - public Class getSettingsFragment() { - return MultimediaNotesFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.MULTIMEDIA_NOTES; } @Override diff --git a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java index 6786e78d70..b7f0e0e0f8 100644 --- a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java +++ b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java @@ -14,7 +14,7 @@ import net.osmand.plus.Version; import net.osmand.plus.activities.ContributionVersionActivity; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.dashboard.tools.DashFragmentData; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; @@ -127,8 +127,8 @@ public class OsmandDevelopmentPlugin extends OsmandPlugin { } @Override - public Class getSettingsFragment() { - return DevelopmentSettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.DEVELOPMENT_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 94987d1c8a..088a9bd31e 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -42,7 +42,7 @@ import net.osmand.plus.activities.SavingTrackHelper.SaveGpxResult; import net.osmand.plus.dashboard.tools.DashFragmentData; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.layers.MapInfoLayer; @@ -173,8 +173,8 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { } @Override - public Class getSettingsFragment() { - return MonitoringSettingsFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.MONITORING_SETTINGS; } @Override diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java index 5030530f88..70f7cd5764 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java @@ -13,9 +13,11 @@ import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; + import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; import net.osmand.data.Amenity; @@ -23,13 +25,11 @@ import net.osmand.data.MapObject; import net.osmand.data.TransportStop; import net.osmand.osm.PoiType; import net.osmand.osm.edit.Entity; -import net.osmand.plus.*; +import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuAdapter.ItemClickListener; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.settings.backend.OsmandPreference; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.activities.EnumAdapter; import net.osmand.plus.activities.EnumAdapter.IEnumWithResource; @@ -42,16 +42,21 @@ import net.osmand.plus.myplaces.AvailableGPXFragment.GpxInfo; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.osmedit.OsmPoint.Action; import net.osmand.plus.quickaction.QuickActionType; +import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; + import org.apache.commons.logging.Log; import java.util.ArrayList; import java.util.List; -import static net.osmand.aidlapi.OsmAndCustomizationConstants.*; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_CREATE_POI; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_OPEN_OSM_NOTE; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_EDITS; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_NOTES; import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction; @@ -198,8 +203,8 @@ public class OsmEditingPlugin extends OsmandPlugin { } @Override - public Class getSettingsFragment() { - return OsmEditingFragment.class; + public SettingsScreenType getSettingsScreenType() { + return SettingsScreenType.OPEN_STREET_MAP_EDITING; } @Override diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java index 86940e7d7a..de995c2ea0 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java @@ -53,11 +53,7 @@ import com.google.android.material.snackbar.Snackbar; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; import net.osmand.access.AccessibilitySettingsFragment; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.settings.backend.OsmandPreference; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; @@ -69,6 +65,10 @@ import net.osmand.plus.monitoring.MonitoringSettingsFragment; import net.osmand.plus.osmedit.OsmEditingFragment; import net.osmand.plus.profiles.SelectAppModesBottomSheetDialogFragment; import net.osmand.plus.profiles.SelectAppModesBottomSheetDialogFragment.AppModeChangedListener; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.CommonPreference; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.bottomsheets.BooleanPreferenceBottomSheet; import net.osmand.plus.settings.bottomsheets.ChangeGeneralProfilesPrefBottomSheet; import net.osmand.plus.settings.bottomsheets.EditTextPreferenceBottomSheet; @@ -112,7 +112,7 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat impl public enum SettingsScreenType { - MAIN_SETTINGS(MainSettingsFragment.TAG, false, null, R.xml.settings_main_screen, R.layout.global_preference_toolbar), + MAIN_SETTINGS(MainSettingsFragment.class.getName(), false, null, R.xml.settings_main_screen, R.layout.global_preference_toolbar), GLOBAL_SETTINGS(GlobalSettingsFragment.class.getName(), false, null, R.xml.global_settings, R.layout.global_preference_toolbar), CONFIGURE_PROFILE(ConfigureProfileFragment.class.getName(), true, null, R.xml.configure_profile, R.layout.profile_preference_toolbar_with_switch), PROXY_SETTINGS(ProxySettingsFragment.class.getName(), false, null, R.xml.proxy_preferences, R.layout.global_preferences_toolbar_with_switch), diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java index e65a351041..1b1a059488 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java @@ -368,7 +368,7 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co } List plugins = OsmandPlugin.getVisiblePlugins(); for (OsmandPlugin plugin : plugins) { - if (plugin instanceof SkiMapsPlugin || plugin instanceof NauticalMapsPlugin || plugin.getSettingsFragment() == null) { + if (plugin instanceof SkiMapsPlugin || plugin instanceof NauticalMapsPlugin || plugin.getSettingsScreenType() == null) { continue; } Preference preference = new Preference(ctx); @@ -378,7 +378,7 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co preference.setSummary(plugin.getPrefsDescription()); preference.setIcon(getContentIcon(plugin.getLogoResourceId())); preference.setLayoutResource(R.layout.preference_with_descr); - preference.setFragment(plugin.getSettingsFragment().getName()); + preference.setFragment(plugin.getSettingsScreenType().fragmentName); preferenceCategory.addPreference(preference); } From a6d117f5a1ecc18dee7e664474f62a456c777849 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Mon, 12 Oct 2020 17:18:01 +0300 Subject: [PATCH 005/123] refactor GraphType --- OsmAnd/res/values/strings.xml | 2 +- .../plus/activities/SettingsBaseActivity.java | 21 +- .../MeasurementEditingContext.java | 68 ++--- .../plus/measurementtool/MtGraphFragment.java | 254 ++++++++---------- 4 files changed, 148 insertions(+), 197 deletions(-) diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 9d8aa0e70c..da8878c111 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,7 +11,7 @@ Thx - Hardy --> - Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it. + %1$s data available only on the roads, you need to calculate a route using “Route between points” to get it. Graph Logout successful Clear OpenStreetMap OAuth token diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java index 1750787774..316de777b0 100644 --- a/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/SettingsBaseActivity.java @@ -189,9 +189,11 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity if(propertyValue == null) { return ""; } - int valueId = getStringRouteInfoPropertyValueId(propertyValue); - if (valueId != -1) { - return ctx.getString(valueId); + final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_"); + Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name"); + if (f != null) { + Integer in = (Integer) f.get(null); + return ctx.getString(in); } } catch (Exception e) { System.err.println(e.getMessage()); @@ -199,19 +201,6 @@ public abstract class SettingsBaseActivity extends ActionBarPreferenceActivity return propertyValue; } - public static int getStringRouteInfoPropertyValueId(String propertyValue) { - try { - final String propertyValueReplaced = propertyValue.replaceAll("\\s+","_"); - Field f = R.string.class.getField("routeInfo_" + propertyValueReplaced + "_name"); - if (f != null) { - return (Integer) f.get(null); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - } - return -1; - } - public void registerListPreference(OsmandPreference b, PreferenceGroup screen, String[] names, T[] values) { ListPreference p = (ListPreference) screen.findPreference(b.getId()); prepareListPreference(b, names, values, p); diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java index f87c324c92..66ced9ebd1 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java @@ -314,37 +314,6 @@ public class MeasurementEditingContext { } public List getAllRouteSegments() { - class TmpRouteSegmentData { - private WptPt start; - private WptPt end; - private List routeSegments; - - public TmpRouteSegmentData(WptPt start, WptPt end, - List routeSegments) { - this.start = start; - this.end = end; - this.routeSegments = new ArrayList<>(routeSegments); - } - - boolean isAfterOf(TmpRouteSegmentData other) { - return Algorithms.objectEquals(this.start, other.end); - } - - boolean isBeforeOf(TmpRouteSegmentData other) { - return Algorithms.objectEquals(this.end, other.start); - } - - void joinAfter(TmpRouteSegmentData other) { - end = other.end; - routeSegments.addAll(other.routeSegments); - } - - void joinBefore(TmpRouteSegmentData other) { - start = other.start; - routeSegments.addAll(0, other.routeSegments); - } - } - // prepare data for sorting List fullList = new ArrayList<>(); for (Map.Entry, RoadSegmentData> entry : roadSegmentData.entrySet()) { @@ -353,7 +322,6 @@ public class MeasurementEditingContext { entry.getKey().second, entry.getValue().getSegments())); } - // sorting data by connecting together while (fullList.size() > 1) { TmpRouteSegmentData firstInList = fullList.get(0); @@ -375,8 +343,42 @@ public class MeasurementEditingContext { } } } + return fullList.size() > 0 ? fullList.get(0).getRouteSegments() : null; + } - return fullList.size() > 0 ? fullList.get(0).routeSegments : null; + private static class TmpRouteSegmentData { + private WptPt start; + private WptPt end; + private List routeSegments; + + public TmpRouteSegmentData(WptPt start, WptPt end, + List routeSegments) { + this.start = start; + this.end = end; + this.routeSegments = new ArrayList<>(routeSegments); + } + + boolean isAfterOf(TmpRouteSegmentData other) { + return Algorithms.objectEquals(this.start, other.end); + } + + boolean isBeforeOf(TmpRouteSegmentData other) { + return Algorithms.objectEquals(this.end, other.start); + } + + void joinAfter(TmpRouteSegmentData other) { + end = other.end; + routeSegments.addAll(other.routeSegments); + } + + void joinBefore(TmpRouteSegmentData other) { + start = other.start; + routeSegments.addAll(0, other.routeSegments); + } + + public List getRouteSegments() { + return routeSegments; + } } void splitSegments(int position) { diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index 72cc761c84..0bda872c2d 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -41,9 +41,7 @@ import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.Ho import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class MtGraphFragment extends Fragment implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { @@ -55,62 +53,26 @@ public class MtGraphFragment extends Fragment private View messageContainer; private LineChart commonGraphChart; private HorizontalBarChart customGraphChart; - private RecyclerView rvMenu; + private RecyclerView rvGraphTypesMenu; private boolean nightMode; private MeasurementEditingContext editingCtx; private GraphType currentGraphType; - private Map graphData = new HashMap<>(); + private List graphTypes = new ArrayList<>(); - private enum GraphType { - OVERVIEW(R.string.shared_string_overview, false, false), - ALTITUDE(R.string.altitude, false, true), -// SLOPE(R.string.shared_string_slope, false, true), - SPEED(R.string.map_widget_speed, false, false), + private enum CommonGraphType { + OVERVIEW(R.string.shared_string_overview, false), + ALTITUDE(R.string.altitude, true), + SLOPE(R.string.shared_string_slope, true), + SPEED(R.string.map_widget_speed, false); - SURFACE(R.string.routeInfo_surface_name, true, false), - ROAD_TYPE(R.string.routeInfo_roadClass_name, true, false), - STEEPNESS(R.string.routeInfo_steepness_name, true, false), - SMOOTHNESS(R.string.routeInfo_smoothness_name, true, false); - - GraphType(int titleId, boolean isCustomType, boolean canBeCalculated) { + CommonGraphType(int titleId, boolean canBeCalculated) { this.titleId = titleId; - this.isCustomType = isCustomType; this.canBeCalculated = canBeCalculated; } final int titleId; - final boolean isCustomType; final boolean canBeCalculated; - - private static List commonTypes; - private static List customTypes; - - static List getCommonTypes() { - if (commonTypes == null) { - prepareLists(); - } - return commonTypes; - } - - static List getCustomTypes() { - if (customTypes == null) { - prepareLists(); - } - return customTypes; - } - - private static void prepareLists() { - commonTypes = new ArrayList<>(); - customTypes = new ArrayList<>(); - for (GraphType type : values()) { - if (type.isCustomType) { - customTypes.add(type); - } else { - commonTypes.add(type); - } - } - } } @Nullable @@ -136,35 +98,33 @@ public class MtGraphFragment extends Fragment customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); updateGraphData(); - rvMenu = view.findViewById(R.id.graph_types_recycler_view); - rvMenu.setLayoutManager( + rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); + rvGraphTypesMenu.setLayoutManager( new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); - prepareGraphTypesSelectionMenu(); - setupVisibleGraphType(GraphType.OVERVIEW); + refreshGraphTypesSelectionMenu(); + setupVisibleGraphType(graphTypes.get(0)); return view; } - private void prepareGraphTypesSelectionMenu() { - rvMenu.removeAllViews(); + private void refreshGraphTypesSelectionMenu() { + rvGraphTypesMenu.removeAllViews(); OsmandApplication app = getMyApplication(); int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode); final ArrayList items = new ArrayList<>(); - for (GraphType type : GraphType.values()) { - String title = getString(type.titleId); - HorizontalSelectionItem item = new HorizontalSelectionItem(title, type); - if (type.isCustomType) { + for (GraphType type : graphTypes) { + HorizontalSelectionItem item = new HorizontalSelectionItem(type.getTitle(), type); + if (type.isCustom()) { item.setTitleColorId(activeColorId); } - if (isDataAvailableFor(type) || type.canBeCalculated) { + if (type.hasData() || type.canBeCalculated) { items.add(item); } } adapter.setItems(items); - String selectedItemKey = currentGraphType != null ? - getString(currentGraphType.titleId) : items.get(0).getTitle(); + String selectedItemKey = currentGraphType != null ? currentGraphType.getTitle() : items.get(0).getTitle(); adapter.setSelectedItemByTitle(selectedItemKey); adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override @@ -177,53 +137,52 @@ public class MtGraphFragment extends Fragment } } }); - rvMenu.setAdapter(adapter); + rvGraphTypesMenu.setAdapter(adapter); adapter.notifyDataSetChanged(); } @Override public void onUpdateAdditionalInfo() { updateGraphData(); - prepareGraphTypesSelectionMenu(); + refreshGraphTypesSelectionMenu(); setupVisibleGraphType(currentGraphType); } private void setupVisibleGraphType(GraphType preferredType) { - currentGraphType = isDataAvailableFor(preferredType) ? - preferredType : getFirstAvailableGraphType(); + currentGraphType = preferredType.hasData() ? preferredType : getFirstAvailableGraphType(); updateDataView(); } private GraphType getFirstAvailableGraphType() { - for (GraphType type : GraphType.values()) { - if (isDataAvailableFor(type) || type.canBeCalculated) { + for (GraphType type : graphTypes) { + if (type.hasData() || type.canBeCalculated()) { return type; } } - return GraphType.OVERVIEW; + return null; } private void updateDataView() { - if (isDataAvailableFor(currentGraphType)) { + if (currentGraphType.hasData()) { showGraph(); - } else if (currentGraphType.canBeCalculated) { + } else if (currentGraphType.canBeCalculated()) { showMessage(); } } private void showGraph() { - if (currentGraphType.isCustomType) { + if (currentGraphType.isCustom()) { customGraphChart.clear(); commonGraphContainer.setVisibility(View.GONE); customGraphContainer.setVisibility(View.VISIBLE); messageContainer.setVisibility(View.GONE); - prepareCustomGraphView(); + prepareCustomGraphView((BarData) currentGraphType.getData()); } else { commonGraphChart.clear(); commonGraphContainer.setVisibility(View.VISIBLE); customGraphContainer.setVisibility(View.GONE); messageContainer.setVisibility(View.GONE); - prepareCommonGraphView(); + prepareCommonGraphView((LineData) currentGraphType.getData()); } } @@ -233,24 +192,19 @@ public class MtGraphFragment extends Fragment messageContainer.setVisibility(View.VISIBLE); TextView tvMessage = messageContainer.findViewById(R.id.message_text); ImageView icon = messageContainer.findViewById(R.id.message_icon); - if (GraphType.ALTITUDE.equals(currentGraphType)) { - tvMessage.setText(R.string.message_need_calculate_route_for_show_graph); - icon.setImageResource(R.drawable.ic_action_altitude_average); - } + String message = getString(R.string.message_need_calculate_route_before_show_graph, currentGraphType.getTitle()); + tvMessage.setText(message); + icon.setImageResource(R.drawable.ic_action_altitude_average); } - private void prepareCommonGraphView() { - LineData data = (LineData) graphData.get(currentGraphType); - if (data == null) return; - + private void prepareCommonGraphView(LineData data) { GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true); commonGraphChart.setData(data); } - private void prepareCustomGraphView() { - BarData data = (BarData) graphData.get(currentGraphType); - OsmandApplication app = getMapActivity().getMyApplication(); - if (data == null || app == null) return; + private void prepareCustomGraphView(BarData data) { + OsmandApplication app = getMyApplication(); + if (app == null) return; GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode); customGraphChart.setExtraRightOffset(16); @@ -259,75 +213,57 @@ public class MtGraphFragment extends Fragment } private void updateGraphData() { + graphTypes.clear(); OsmandApplication app = getMyApplication(); GPXTrackAnalysis analysis = createGpxTrackAnalysis(); // update common graph data - for (GraphType type : GraphType.getCommonTypes()) { - List dataSets = getDataSets(type, commonGraphChart, analysis); + for (CommonGraphType commonType : CommonGraphType.values()) { + List dataSets = getDataSets(commonType, commonGraphChart, analysis); + Object data = null; if (!Algorithms.isEmpty(dataSets)) { - graphData.put(type, new LineData(dataSets)); - } else { - graphData.put(type, null); + data = new LineData(dataSets); } + String title = getString(commonType.titleId); + graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data)); } // update custom graph data List routeSegments = editingCtx.getAllRouteSegments(); List routeStatistics = calculateRouteStatistics(routeSegments); - for (GraphType type : GraphType.getCustomTypes()) { - RouteStatistics statistic = getStatisticForGraphType(routeStatistics, type); - if (statistic != null && !Algorithms.isEmpty(statistic.elements)) { - BarData data = GpxUiHelper.buildStatisticChart( - app, customGraphChart, statistic, analysis, true, nightMode); - graphData.put(type, data); - } else { - graphData.put(type, null); + if (analysis != null && routeStatistics != null) { + for (RouteStatistics statistics : routeStatistics) { + String title = SettingsBaseActivity.getStringRouteInfoPropertyValue(app, statistics.name); + BarData data = null; + if (!Algorithms.isEmpty(statistics.elements)) { + data = GpxUiHelper.buildStatisticChart( + app, customGraphChart, statistics, analysis, true, nightMode); + } + graphTypes.add(new GraphType(title, true, false, data)); } } } - private RouteStatistics getStatisticForGraphType(List routeStatistics, GraphType graphType) { - if (routeStatistics == null) return null; - for (RouteStatistics statistic : routeStatistics) { - int graphTypeId = graphType.titleId; - int statisticId = SettingsBaseActivity.getStringRouteInfoPropertyValueId(statistic.name); - if (graphTypeId == statisticId) { - return statistic; - } - } - return null; - } - - private List getDataSets(GraphType graphType, LineChart chart, GPXTrackAnalysis analysis) { + private List getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) { List dataSets = new ArrayList<>(); if (chart != null && analysis != null) { OsmandApplication app = getMyApplication(); - switch (graphType) { + switch (type) { case OVERVIEW: { - GpxUiHelper.OrderedLineDataSet speedDataSet = null; - GpxUiHelper.OrderedLineDataSet elevationDataSet = null; -// GpxUiHelper.OrderedLineDataSet slopeDataSet = null; - if (analysis.hasSpeedData) { - speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false); - } - if (analysis.hasElevationData) { - elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false); -// slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, -// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); - } List dataList = new ArrayList<>(); - if (speedDataSet != null) { + if (analysis.hasSpeedData) { + GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false); dataList.add(speedDataSet); } - if (elevationDataSet != null) { + if (analysis.hasElevationData) { + GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false); dataList.add(elevationDataSet); + GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); + dataList.add(slopeDataSet); } -// if (slopeDataSet != null) { -// dataList.add(slopeDataSet); -// } if (dataList.size() > 0) { Collections.sort(dataList, new Comparator() { @Override @@ -343,28 +279,22 @@ public class MtGraphFragment extends Fragment if (analysis.hasElevationData) { GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); - if (elevationDataSet != null) { - dataSets.add(elevationDataSet); - } + dataSets.add(elevationDataSet); } break; } -// case SLOPE: -// if (analysis.hasElevationData) { -// GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, -// analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); -// if (slopeDataSet != null) { -// dataSets.add(slopeDataSet); -// } -// } -// break; + case SLOPE: + if (analysis.hasElevationData) { + GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, + analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); + dataSets.add(slopeDataSet); + } + break; case SPEED: { if (analysis.hasSpeedData) { GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); - if (speedDataSet != null) { - dataSets.add(speedDataSet); - } + dataSets.add(speedDataSet); } break; } @@ -398,10 +328,6 @@ public class MtGraphFragment extends Fragment defaultRender, currentSearchRequest, defaultSearchRequest); } - private boolean isDataAvailableFor(GraphType graphType) { - return graphData != null && graphData.get(graphType) != null; - } - private OsmandApplication getMyApplication() { return getMapActivity().getMyApplication(); } @@ -409,4 +335,38 @@ public class MtGraphFragment extends Fragment private MapActivity getMapActivity() { return (MapActivity) getActivity(); } + + private static class GraphType { + private String title; + private boolean isCustom; + private boolean canBeCalculated; + private Object data; + + public GraphType(String title, boolean isCustom, boolean canBeCalculated, Object data) { + this.title = title; + this.isCustom = isCustom; + this.canBeCalculated = canBeCalculated; + this.data = data; + } + + public String getTitle() { + return title; + } + + public boolean isCustom() { + return isCustom; + } + + public boolean canBeCalculated() { + return canBeCalculated; + } + + public boolean hasData() { + return getData() != null; + } + + public Object getData() { + return data; + } + } } From 14d4ca32a8172213c8271053844ea670bc73436a Mon Sep 17 00:00:00 2001 From: xmd5a Date: Mon, 12 Oct 2020 17:37:43 +0300 Subject: [PATCH 006/123] Change rtsa_scale en translations to latin --- OsmAnd/res/values/phrases.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index b58d25a781..87c5c7b4d6 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -3935,20 +3935,20 @@ Tram Ferry - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gas flare;Flare stack Deleted object From a09194402b1b4d9940f3b660b926599408c49e53 Mon Sep 17 00:00:00 2001 From: sergosm Date: Mon, 12 Oct 2020 18:26:43 +0300 Subject: [PATCH 007/123] init --- OsmAnd/res/values/strings.xml | 2 +- .../plus/settings/fragments/RouteParametersFragment.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 582183f383..22b7b37b2f 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,7 @@ Thx - Hardy --> + Use 2-phase A* routing algorithm File is already imported in OsmAnd Logout successful Clear OpenStreetMap OAuth token @@ -20,7 +21,6 @@ Native Public Transport development Recalculates only the initial part of the route. Can be used for long trips. Two-phase routing for car navigation. - Complex routing OsmAnd Live data OsmAnd Live data Development diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java index a17c265645..0e3698644d 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/RouteParametersFragment.java @@ -162,6 +162,7 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useOsmLiveForPublicTransport.setDescription(getString(R.string.use_osm_live_public_transport_description)); useOsmLiveForPublicTransport.setSummaryOn(R.string.shared_string_enabled); useOsmLiveForPublicTransport.setSummaryOff(R.string.shared_string_disabled); + useOsmLiveForPublicTransport.setIcon(getContentIcon(R.drawable.ic_action_osm_live)); useOsmLiveForPublicTransport.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useOsmLiveForPublicTransport); } @@ -184,13 +185,14 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useOsmLiveForRouting.setDescription(getString(R.string.use_osm_live_routing_description)); useOsmLiveForRouting.setSummaryOn(R.string.shared_string_enabled); useOsmLiveForRouting.setSummaryOff(R.string.shared_string_disabled); + useOsmLiveForRouting.setIcon(getContentIcon(R.drawable.ic_action_osm_live)); useOsmLiveForRouting.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useOsmLiveForRouting); } private void setupDisableComplexRoutingPref() { SwitchPreferenceEx disableComplexRouting = createSwitchPreferenceEx(settings.DISABLE_COMPLEX_ROUTING.getId(), - R.string.use_complex_routing, R.layout.preference_with_descr_dialog_and_switch); + R.string.use_two_phase_routing, R.layout.preference_with_descr_dialog_and_switch); disableComplexRouting.setDescription(getString(R.string.complex_routing_descr)); disableComplexRouting.setSummaryOn(R.string.shared_string_enabled); disableComplexRouting.setSummaryOff(R.string.shared_string_disabled); @@ -204,6 +206,7 @@ public class RouteParametersFragment extends BaseSettingsFragment implements OnP useFastRecalculation.setDescription(getString(R.string.use_fast_recalculation_desc)); useFastRecalculation.setSummaryOn(R.string.shared_string_enabled); useFastRecalculation.setSummaryOff(R.string.shared_string_disabled); + useFastRecalculation.setIcon(getContentIcon(R.drawable.ic_action_route_part)); useFastRecalculation.setIconSpaceReserved(true); getPreferenceScreen().addPreference(useFastRecalculation); } From 53a3583b3c8d91d54b0482f46b99c3e97bed0249 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Mon, 12 Oct 2020 18:55:21 +0300 Subject: [PATCH 008/123] Fix API - Margins should not saved after restart #214 --- .../customization/MapMarginsParams.java | 43 +++++++++------ OsmAnd/src/net/osmand/aidl/ConnectedApp.java | 48 ----------------- OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java | 53 ++----------------- .../net/osmand/aidl/OsmandAidlServiceV2.java | 5 +- .../osmand/plus/activities/MapActivity.java | 2 +- .../backend/OsmAndAppCustomization.java | 31 +++++++++++ 6 files changed, 66 insertions(+), 116 deletions(-) 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/src/net/osmand/aidl/ConnectedApp.java b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java index 02f412887e..8d90bab5e2 100644 --- a/OsmAnd/src/net/osmand/aidl/ConnectedApp.java +++ b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java @@ -11,16 +11,12 @@ import android.widget.CompoundButton; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - import net.osmand.AndroidUtils; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.layers.AidlMapLayer; @@ -28,8 +24,6 @@ import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget; import net.osmand.util.Algorithms; -import java.lang.reflect.Type; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -39,7 +33,6 @@ public class ConnectedApp implements Comparable { public static final String AIDL_LAYERS_PREFIX = "aidl_layers_"; public static final String AIDL_WIDGETS_PREFIX = "aidl_widgets_"; - public static final String AIDL_MARGINS_PREFIX = "aidl_margins_"; static final String AIDL_OBJECT_ID = "aidl_object_id"; static final String AIDL_PACKAGE_NAME = "aidl_package_name"; @@ -62,7 +55,6 @@ public class ConnectedApp implements Comparable { private Map mapLayers = new ConcurrentHashMap<>(); private CommonPreference layersPref; - private CommonPreference marginsPref; private String pack; private String name; @@ -76,7 +68,6 @@ public class ConnectedApp implements Comparable { this.pack = pack; this.enabled = enabled; layersPref = app.getSettings().registerBooleanPreference(AIDL_LAYERS_PREFIX + pack, true).cache(); - marginsPref = app.getSettings().registerStringPreference(AIDL_MARGINS_PREFIX + pack, null).cache(); } public boolean isEnabled() { @@ -134,45 +125,6 @@ public class ConnectedApp implements Comparable { } } - void updateMapMargins(@NonNull MapActivity mapActivity) { - String marginsJson = marginsPref.get(); - if (marginsJson != null) { - Type type = new TypeToken>() { - }.getType(); - Map margins = new Gson().fromJson(marginsJson, type); - if (margins != null) { - Integer leftMargin = margins.get("left"); - Integer topMargin = margins.get("top"); - Integer rightMargin = margins.get("right"); - Integer bottomMargin = margins.get("bottom"); - - int left = leftMargin != null ? leftMargin : 0; - int top = topMargin != null ? topMargin : 0; - int right = rightMargin != null ? rightMargin : 0; - int bottom = bottomMargin != null ? bottomMargin : 0; - - mapActivity.setMargins(left, top, right, bottom); - return; - } - } - mapActivity.setMargins(0, 0, 0, 0); - } - - public void setMargins(@NonNull MapActivity mapActivity, String appModeKey, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { - ApplicationMode mode = ApplicationMode.valueOfStringKey(appModeKey, null); - if (mode != null) { - Map margins = new HashMap<>(); - margins.put("left", leftMargin); - margins.put("top", topMargin); - margins.put("right", rightMargin); - margins.put("bottom", bottomMargin); - - String marginsJson = new Gson().toJson(margins); - marginsPref.setModeValue(mode, marginsJson); - updateMapMargins(mapActivity); - } - } - void registerLayerContextMenu(final ContextMenuAdapter menuAdapter, final MapActivity mapActivity) { ContextMenuAdapter.ItemClickListener listener = new ContextMenuAdapter.OnRowItemClick() { diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index 6329d5db2e..5801fc916d 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -200,13 +200,6 @@ public class OsmandAidlApi { private static final String AIDL_QUICK_ACTION_NUMBER = "aidl_quick_action_number"; private static final String AIDL_LOCK_STATE = "lock_state"; - private static final String AIDL_SET_MAP_MARGINS = "set_map_margins"; - private static final String AIDL_APP_MODE = "app_mode"; - private static final String AIDL_LEFT_MARGIN = "left_margin"; - private static final String AIDL_TOP_MARGIN = "top_margin"; - private static final String AIDL_RIGHT_MARGIN = "right_margin"; - private static final String AIDL_BOTTOM_MARGIN = "bottom_margin"; - private static final ApplicationMode DEFAULT_PROFILE = ApplicationMode.CAR; private static final ApplicationMode[] VALID_PROFILES = new ApplicationMode[]{ @@ -257,7 +250,6 @@ public class OsmandAidlApi { registerHideSqliteDbFileReceiver(mapActivity); registerExecuteQuickActionReceiver(mapActivity); registerLockStateReceiver(mapActivity); - registerMapMarginsReceiver(mapActivity); initOsmandTelegram(); app.getAppCustomization().addListener(mapActivity); this.mapActivity = mapActivity; @@ -379,28 +371,10 @@ public class OsmandAidlApi { registerReceiver(addMapWidgetReceiver, mapActivity, AIDL_ADD_MAP_WIDGET); } - private void registerMapMarginsReceiver(MapActivity mapActivity) { - final WeakReference mapActivityRef = new WeakReference<>(mapActivity); - BroadcastReceiver addMapWidgetReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - MapActivity mapActivity = mapActivityRef.get(); - String appModeKey = intent.getStringExtra(AIDL_APP_MODE); - String packName = intent.getStringExtra(AIDL_PACKAGE_NAME); - if (mapActivity != null && appModeKey != null && packName != null) { - ConnectedApp connectedApp = connectedApps.get(packName); - if (connectedApp != null) { - int leftMargin = intent.getIntExtra(AIDL_LEFT_MARGIN, 0); - int topMargin = intent.getIntExtra(AIDL_TOP_MARGIN, 0); - int bottomMargin = intent.getIntExtra(AIDL_RIGHT_MARGIN, 0); - int rightMargin = intent.getIntExtra(AIDL_BOTTOM_MARGIN, 0); - - connectedApp.setMargins(mapActivity, appModeKey, leftMargin, topMargin, rightMargin, bottomMargin); - } - } - } - }; - registerReceiver(addMapWidgetReceiver, mapActivity, AIDL_SET_MAP_MARGINS); + boolean setMapMargins(int left, int top, int right, int bottom, @Nullable List appModeKeys) { + app.getAppCustomization().setMapMargins(left, top, right, bottom, appModeKeys); + app.getAppCustomization().updateMapMargins(mapActivity); + return true; } private void registerAddContextMenuButtonsReceiver(MapActivity mapActivity) { @@ -919,12 +893,6 @@ public class OsmandAidlApi { } } - public void updateMapMargins(@NonNull MapActivity mapActivity) { - for (ConnectedApp connectedApp : connectedApps.values()) { - connectedApp.updateMapMargins(mapActivity); - } - } - private void refreshMap() { Intent intent = new Intent(); intent.setAction(AIDL_REFRESH_MAP); @@ -2330,19 +2298,6 @@ public class OsmandAidlApi { return true; } - public boolean setMapMargins(String packName, String appModeKey, int leftMargin, int topMargin, int bottomMargin, int rightMargin) { - Intent intent = new Intent(); - intent.setAction(AIDL_SET_MAP_MARGINS); - intent.putExtra(AIDL_PACKAGE_NAME, packName); - intent.putExtra(AIDL_APP_MODE, appModeKey); - intent.putExtra(AIDL_LEFT_MARGIN, leftMargin); - intent.putExtra(AIDL_TOP_MARGIN, topMargin); - intent.putExtra(AIDL_RIGHT_MARGIN, bottomMargin); - intent.putExtra(AIDL_BOTTOM_MARGIN, rightMargin); - app.sendBroadcast(intent); - return true; - } - public boolean exportProfile(String appModeKey, List settingsTypesKeys) { ApplicationMode appMode = ApplicationMode.valueOfStringKey(appModeKey, null); if (app != null && appMode != null) { diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java index 7c69be1e94..b904ef0517 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java @@ -1327,9 +1327,8 @@ public class OsmandAidlServiceV2 extends Service implements AidlCallbackListener public boolean setMapMargins(MapMarginsParams params) { try { OsmandAidlApi api = getApi("setMapMargins"); - String packName = getCallingAppPackName(); - return api != null && api.setMapMargins(packName, params.getAppModeKey(), params.getLeftMargin(), - params.getTopMargin(), params.getBottomMargin(), params.getRightMargin()); + return api != null && api.setMapMargins(params.getLeftMargin(), params.getTopMargin(), + params.getBottomMargin(), params.getRightMargin(), params.getAppModesKeys()); } catch (Exception e) { handleException(e); return false; diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 3baa33cb32..e090666915 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -1419,7 +1419,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven }); getMapView().refreshMap(true); applyScreenOrientation(); - app.getAidlApi().updateMapMargins(this); + app.getAppCustomization().updateMapMargins(this); } public void updateNavigationBarColor() { diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java index b23edbbea1..4ee7827668 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java @@ -81,10 +81,16 @@ public class OsmAndAppCustomization { private Set featuresDisabledIds = new HashSet<>(); private Set featuresEnabledPatterns = new HashSet<>(); private Set featuresDisabledPatterns = new HashSet<>(); + private Set marginAppModeUsage = new HashSet<>(); private Map> widgetsVisibilityMap = new LinkedHashMap<>(); private Map> widgetsAvailabilityMap = new LinkedHashMap<>(); private CustomOsmandSettings customOsmandSettings; + private int marginLeft; + private int marginTop; + private int marginRight; + private int marginBottom; + private boolean featuresCustomized; private boolean widgetsCustomized; @@ -151,6 +157,10 @@ public class OsmAndAppCustomization { featuresCustomized = false; widgetsCustomized = false; customOsmandSettings = null; + marginLeft = 0; + marginTop = 0; + marginRight = 0; + marginBottom = 0; restoreOsmandSettings(); featuresEnabledIds.clear(); @@ -159,6 +169,7 @@ public class OsmAndAppCustomization { featuresDisabledPatterns.clear(); widgetsVisibilityMap.clear(); widgetsAvailabilityMap.clear(); + marginAppModeUsage.clear(); return true; } @@ -368,6 +379,26 @@ public class OsmAndAppCustomization { return set; } + public void setMapMargins(int left, int top, int right, int bottom, List appModeKeys) { + marginLeft = left; + marginTop = top; + marginRight = right; + marginBottom = bottom; + marginAppModeUsage.addAll(getAppModesSet(appModeKeys)); + } + + public void updateMapMargins(MapActivity mapActivity) { + if (isMapMarginAvailable()) { + mapActivity.setMargins(marginLeft, marginTop, marginRight, marginBottom); + } else { + mapActivity.setMargins(0, 0, 0, 0); + } + } + + boolean isMapMarginAvailable() { + return marginAppModeUsage.contains(app.getSettings().getApplicationMode()); + } + public boolean isWidgetVisible(@NonNull String key, ApplicationMode appMode) { Set set = widgetsVisibilityMap.get(key); if (set == null) { From f5cacff2cda159b29b447b5a24654fdbde5d45e2 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Mon, 12 Oct 2020 19:23:04 +0300 Subject: [PATCH 009/123] use cards instead of fragment --- .../res/layout/fragment_measurement_tool.xml | 2 +- .../MeasurementToolFragment.java | 82 ++++++------------ .../plus/measurementtool/MtGraphFragment.java | 83 ++++++++----------- .../measurementtool/MtPointsFragment.java | 54 ++++-------- 4 files changed, 81 insertions(+), 140 deletions(-) diff --git a/OsmAnd/res/layout/fragment_measurement_tool.xml b/OsmAnd/res/layout/fragment_measurement_tool.xml index 1ccae4f0c3..a6e1e15018 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool.xml @@ -144,7 +144,7 @@ android:layout_height="@dimen/content_padding_small" /> diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index a927ca884e..e5737ceca1 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -26,7 +26,6 @@ import androidx.core.content.ContextCompat; import androidx.core.widget.TextViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -72,6 +71,7 @@ import net.osmand.plus.measurementtool.command.MovePointCommand; import net.osmand.plus.measurementtool.command.RemovePointCommand; import net.osmand.plus.measurementtool.command.ReorderPointCommand; import net.osmand.plus.measurementtool.command.ReversePointsCommand; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.views.layers.MapControlsLayer; @@ -116,6 +116,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private TextView distanceToCenterTv; private String pointsSt; private View additionalInfoContainer; + private ViewGroup additionalInfoCardsContainer; + private BaseCard visibleAdditionalInfoCard; private LinearLayout customRadioButton; private View mainView; private ImageView upDownBtn; @@ -157,18 +159,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } private enum AdditionalInfoType { - POINTS(MtPointsFragment.class.getName()), - GRAPH(MtGraphFragment.class.getName()); - - AdditionalInfoType(String fragmentName) { - this.fragmentName = fragmentName; - } - - final String fragmentName; - - public String getFragmentName() { - return fragmentName; - } + POINTS, + GRAPH } private void setEditingCtx(MeasurementEditingContext editingCtx) { @@ -264,6 +256,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route mainView = view.findViewById(R.id.main_view); AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark); additionalInfoContainer = mainView.findViewById(R.id.additional_info_container); + additionalInfoCardsContainer = mainView.findViewById(R.id.cards_container); if (portrait) { customRadioButton = mainView.findViewById(R.id.custom_radio_buttons); @@ -521,7 +514,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) { if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) { currentAdditionalInfoType = type; - additionalInfoExpanded = true; updateUpDownBtn(); OsmandApplication app = getMyApplication(); if (AdditionalInfoType.POINTS.equals(type)) { @@ -531,14 +523,13 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } else { return; } - setAdditionalInfoFragment(type.getFragmentName()); + setAdditionalInfoCard(type); } } private void updateAdditionalInfoView() { - Fragment fragment = getActiveAdditionalInfoFragment(); - if (fragment instanceof OnUpdateAdditionalInfoListener) { - ((OnUpdateAdditionalInfoListener) fragment).onUpdateAdditionalInfo(); + if (visibleAdditionalInfoCard instanceof OnUpdateAdditionalInfoListener) { + ((OnUpdateAdditionalInfoListener) visibleAdditionalInfoCard).onUpdateAdditionalInfo(); } } @@ -1472,29 +1463,28 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route if (portrait) { additionalInfoExpanded = false; updateUpDownBtn(); - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - Fragment activeFragment = getActiveAdditionalInfoFragment(); - if (activeFragment != null) { - FragmentManager manager = getChildFragmentManager(); - manager.beginTransaction().remove(activeFragment).commitAllowingStateLoss(); - } - additionalInfoContainer.setVisibility(View.GONE); - setDefaultMapPosition(); - } + additionalInfoContainer.setVisibility(View.GONE); + setDefaultMapPosition(); } } - private void setAdditionalInfoFragment(String fragmentName) { - Context ctx = getContext(); - if (ctx == null) return; + private void setAdditionalInfoCard(AdditionalInfoType type) { + MapActivity ma = getMapActivity(); + if (ma == null) return; - Fragment fragment = Fragment.instantiate(ctx, fragmentName); - FragmentManager fm = getChildFragmentManager(); - FragmentTransaction fragmentTransaction = fm.beginTransaction(); - fragmentTransaction.replace(R.id.fragmentContainer, fragment, fragmentName); - fragmentTransaction.commit(); - fm.executePendingTransactions(); + BaseCard additionalInfoCard = null; + if (type.equals(AdditionalInfoType.POINTS)) { + additionalInfoCard = new MtPointsFragment(ma, this); + } else if (type.equals(AdditionalInfoType.GRAPH)) { + additionalInfoCard = new MtGraphFragment(ma, this); + } + + if (additionalInfoCard != null) { + visibleAdditionalInfoCard = additionalInfoCard; + additionalInfoCardsContainer.removeAllViews(); + additionalInfoCardsContainer.addView(additionalInfoCard.build(ma)); + additionalInfoExpanded = true; + } } private void collapseAdditionalInfoIfNoPointsEnough() { @@ -1512,24 +1502,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } } - private Fragment getActiveAdditionalInfoFragment() { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - for (AdditionalInfoType type : AdditionalInfoType.values()) { - try { - FragmentManager fm = getChildFragmentManager(); - Fragment fragment = fm.findFragmentByTag(type.getFragmentName()); - if (fragment != null) { - return fragment; - } - } catch (Exception e) { - // ignore - } - } - } - return null; - } - private void setDefaultMapPosition() { setMapPosition(OsmandSettings.CENTER_CONSTANT); } diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index 0bda872c2d..71a7c45db2 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -1,15 +1,10 @@ package net.osmand.plus.measurementtool; -import android.os.Bundle; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -21,12 +16,12 @@ import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.SettingsBaseActivity; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; import net.osmand.plus.render.MapRenderRepositories; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.render.RenderingRuleSearchRequest; import net.osmand.render.RenderingRulesStorage; import net.osmand.router.RouteSegmentResult; @@ -43,7 +38,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public class MtGraphFragment extends Fragment +public class MtGraphFragment extends BaseCard implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp"; @@ -55,10 +50,15 @@ public class MtGraphFragment extends Fragment private HorizontalBarChart customGraphChart; private RecyclerView rvGraphTypesMenu; - private boolean nightMode; private MeasurementEditingContext editingCtx; private GraphType currentGraphType; private List graphTypes = new ArrayList<>(); + private MeasurementToolFragment mtf; + + public MtGraphFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { + super(mapActivity); + this.mtf = mtf; + } private enum CommonGraphType { OVERVIEW(R.string.shared_string_overview, false), @@ -75,39 +75,6 @@ public class MtGraphFragment extends Fragment final boolean canBeCalculated; } - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - - final MapActivity mapActivity = (MapActivity) getActivity(); - final MeasurementToolFragment mtf = (MeasurementToolFragment) getParentFragment(); - if (mapActivity == null || mtf == null) return null; - - editingCtx = mtf.getEditingCtx(); - OsmandApplication app = mapActivity.getMyApplication(); - - nightMode = app.getDaynightHelper().isNightModeForMapControls(); - View view = UiUtilities.getInflater(app, nightMode).inflate( - R.layout.fragment_measurement_tool_graph, container, false); - commonGraphContainer = view.findViewById(R.id.common_graphs_container); - customGraphContainer = view.findViewById(R.id.custom_graphs_container); - messageContainer = view.findViewById(R.id.message_container); - commonGraphChart = (LineChart) view.findViewById(R.id.line_chart); - customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); - updateGraphData(); - - rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); - rvGraphTypesMenu.setLayoutManager( - new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); - - refreshGraphTypesSelectionMenu(); - setupVisibleGraphType(graphTypes.get(0)); - - return view; - } - private void refreshGraphTypesSelectionMenu() { rvGraphTypesMenu.removeAllViews(); OsmandApplication app = getMyApplication(); @@ -149,7 +116,8 @@ public class MtGraphFragment extends Fragment } private void setupVisibleGraphType(GraphType preferredType) { - currentGraphType = preferredType.hasData() ? preferredType : getFirstAvailableGraphType(); + currentGraphType = currentGraphType != null && preferredType.hasData() + ? preferredType : getFirstAvailableGraphType(); updateDataView(); } @@ -192,7 +160,7 @@ public class MtGraphFragment extends Fragment messageContainer.setVisibility(View.VISIBLE); TextView tvMessage = messageContainer.findViewById(R.id.message_text); ImageView icon = messageContainer.findViewById(R.id.message_icon); - String message = getString(R.string.message_need_calculate_route_before_show_graph, currentGraphType.getTitle()); + String message = app.getString(R.string.message_need_calculate_route_before_show_graph, currentGraphType.getTitle()); tvMessage.setText(message); icon.setImageResource(R.drawable.ic_action_altitude_average); } @@ -224,7 +192,7 @@ public class MtGraphFragment extends Fragment if (!Algorithms.isEmpty(dataSets)) { data = new LineData(dataSets); } - String title = getString(commonType.titleId); + String title = app.getString(commonType.titleId); graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data)); } @@ -328,12 +296,31 @@ public class MtGraphFragment extends Fragment defaultRender, currentSearchRequest, defaultSearchRequest); } - private OsmandApplication getMyApplication() { - return getMapActivity().getMyApplication(); + @Override + public int getCardLayoutId() { + return R.layout.fragment_measurement_tool_graph; } - private MapActivity getMapActivity() { - return (MapActivity) getActivity(); + @Override + protected void updateContent() { + if (mapActivity == null || mtf == null) return; + + editingCtx = mtf.getEditingCtx(); + OsmandApplication app = mapActivity.getMyApplication(); + + commonGraphContainer = view.findViewById(R.id.common_graphs_container); + customGraphContainer = view.findViewById(R.id.custom_graphs_container); + messageContainer = view.findViewById(R.id.message_container); + commonGraphChart = (LineChart) view.findViewById(R.id.line_chart); + customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); + updateGraphData(); + + rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); + rvGraphTypesMenu.setLayoutManager( + new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); + + refreshGraphTypesSelectionMenu(); + setupVisibleGraphType(currentGraphType); } private static class GraphType { diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java index 086ab6c322..c859d9b84d 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java @@ -1,46 +1,41 @@ package net.osmand.plus.measurementtool; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; -public class MtPointsFragment extends Fragment +public class MtPointsFragment extends BaseCard implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { - private boolean nightMode; private MeasurementToolAdapter adapter; private MeasurementEditingContext editingCtx; private RecyclerView pointsRv; + private MeasurementToolFragment mtf; + + public MtPointsFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { + super(mapActivity); + this.mtf = mtf; + } - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public void onUpdateAdditionalInfo() { + adapter.notifyDataSetChanged(); + } - final MapActivity mapActivity = (MapActivity) getActivity(); - final MeasurementToolFragment mtf = (MeasurementToolFragment) getParentFragment(); - if (mapActivity == null || mtf == null) { - return null; - } - nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls(); - View view = UiUtilities.getInflater(getContext(), nightMode) - .inflate(R.layout.fragment_measurement_tool_points_list, container, false); + @Override + public int getCardLayoutId() { + return R.layout.fragment_measurement_tool_points_list; + } + @Override + protected void updateContent() { editingCtx = mtf.getEditingCtx(); final GpxData gpxData = editingCtx.getGpxData(); adapter = new MeasurementToolAdapter(mapActivity, editingCtx.getPoints(), @@ -49,20 +44,7 @@ public class MtPointsFragment extends Fragment ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); touchHelper.attachToRecyclerView(pointsRv); adapter.setAdapterListener(mtf.createMeasurementAdapterListener(touchHelper)); - pointsRv.setLayoutManager(new LinearLayoutManager(getContext())); + pointsRv.setLayoutManager(new LinearLayoutManager(app)); pointsRv.setAdapter(adapter); - - return view; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - adapter.setAdapterListener(null); - } - - @Override - public void onUpdateAdditionalInfo() { - adapter.notifyDataSetChanged(); } } From 08d6e1390d9a5d096553d4b5c5426ca08ce3d944 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 12 Oct 2020 21:32:10 +0300 Subject: [PATCH 010/123] Add plugins info fragments --- OsmAnd/res/layout/plugin.xml | 367 +++++++++--------- OsmAnd/res/layout/plugins.xml | 22 +- OsmAnd/res/layout/plugins_list_item.xml | 1 + .../plus/activities/MapActivityActions.java | 6 +- .../plus/activities/PluginInfoFragment.java | 255 ++++++++++++ .../plus/activities/PluginsFragment.java | 338 ++++++++++++++++ .../plus/activities/SettingsActivity.java | 9 - .../plus/dashboard/DashPluginsFragment.java | 7 +- .../plus/download/ui/ItemViewHolder.java | 16 +- .../net/osmand/plus/helpers/IntentHelper.java | 31 +- .../controllers/MapDataMenuController.java | 4 +- .../backend/OsmAndAppCustomization.java | 7 +- .../fragments/ConfigureMenuRootFragment.java | 17 +- 13 files changed, 857 insertions(+), 223 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java create mode 100644 OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java diff --git a/OsmAnd/res/layout/plugin.xml b/OsmAnd/res/layout/plugin.xml index b0436d95e5..710015345c 100644 --- a/OsmAnd/res/layout/plugin.xml +++ b/OsmAnd/res/layout/plugin.xml @@ -1,186 +1,205 @@ - + - + - + + + + + + + android:layout_height="wrap_content" + android:orientation="vertical" + tools:context=".activities.PluginActivity"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginStart="@dimen/content_padding" + android:layout_marginLeft="@dimen/content_padding" + android:layout_marginTop="@dimen/content_padding" + android:layout_marginEnd="@dimen/content_padding" + android:layout_marginRight="@dimen/content_padding" + android:text="@string/shared_string_description" + android:textColor="?android:textColorSecondary" + android:textSize="@dimen/default_desc_text_size" + osmand:textAllCapsCompat="true" + osmand:typeface="@string/font_roboto_medium" /> - + - + - + - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/OsmAnd/res/layout/plugins.xml b/OsmAnd/res/layout/plugins.xml index 095bfee61c..66d220fce8 100644 --- a/OsmAnd/res/layout/plugins.xml +++ b/OsmAnd/res/layout/plugins.xml @@ -1,14 +1,24 @@ - + + + + + + - + android:dividerHeight="1dp" + android:drawSelectorOnTop="true" /> + + \ No newline at end of file diff --git a/OsmAnd/res/layout/plugins_list_item.xml b/OsmAnd/res/layout/plugins_list_item.xml index c616b0014f..be5e2e575a 100644 --- a/OsmAnd/res/layout/plugins_list_item.xml +++ b/OsmAnd/res/layout/plugins_list_item.xml @@ -52,6 +52,7 @@ android:ellipsize="end" android:lines="2" android:maxLines="2" + android:scrollbars="none" android:text="@string/lorem_ipsum" android:textColor="?android:textColorSecondary" android:textSize="@dimen/default_desc_text_size" diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java index d9137ee2d6..09cdaad519 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java @@ -974,10 +974,8 @@ public class MapActivityActions implements DialogProvider { @Override public boolean onContextMenuClick(ArrayAdapter adapter, int itemId, int pos, boolean isChecked, int[] viewCoordinates) { app.logEvent("drawer_plugins_open"); - Intent newIntent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization() - .getPluginsActivity()); - newIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - mapActivity.startActivity(newIntent); + MapActivity.clearPrevActivityIntent(); + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); return true; } }).createItem()); diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java new file mode 100644 index 0000000000..ff396a3ed0 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java @@ -0,0 +1,255 @@ +package net.osmand.plus.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; +import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog.PluginStateListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; +import net.osmand.plus.srtmplugin.SRTMPlugin; + +import org.apache.commons.logging.Log; + +public class PluginInfoFragment extends BaseOsmAndFragment implements PluginStateListener { + + private static final Log log = PlatformUtil.getLog(PluginInfoFragment.class); + + private static final String TAG = PluginInfoFragment.class.getName(); + + public static final String EXTRA_PLUGIN_ID = "plugin_id"; + + private OsmandPlugin plugin; + private OsmandApplication app; + + private View mainView; + private boolean nightMode; + + @Override + public int getStatusBarColorId() { + return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentActivity activity = requireMyActivity(); + activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + dismiss(); + } + }); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + app = requireMyApplication(); + + Bundle args = getArguments(); + if (args == null || !args.containsKey(EXTRA_PLUGIN_ID)) { + log.error("Required extra '" + EXTRA_PLUGIN_ID + "' is missing"); + return null; + } + String pluginId = args.getString(EXTRA_PLUGIN_ID); + if (pluginId == null) { + log.error("Extra '" + EXTRA_PLUGIN_ID + "' is null"); + return null; + } + plugin = OsmandPlugin.getPlugin(pluginId); + if (plugin == null) { + log.error("Plugin '" + EXTRA_PLUGIN_ID + "' not found"); + return null; + } + + Context context = requireContext(); + nightMode = !app.getSettings().isLightContent(); + LayoutInflater themedInflater = UiUtilities.getInflater(context, nightMode); + mainView = themedInflater.inflate(R.layout.plugin, container, false); + AndroidUtils.addStatusBarPadding21v(context, mainView); + + TextView toolbarTitle = mainView.findViewById(R.id.toolbar_title); + toolbarTitle.setText(plugin.getName()); + + ImageView closeButton = mainView.findViewById(R.id.close_button); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Activity activity = getMyActivity(); + if (activity != null) { + activity.onBackPressed(); + } + } + }); + UiUtilities.rotateImageByLayoutDirection(closeButton, AndroidUtils.getLayoutDirection(app)); + + Drawable pluginImage = plugin.getAssetResourceImage(); + if (pluginImage != null) { + ImageView img = mainView.findViewById(R.id.plugin_image); + img.setImageDrawable(pluginImage); + } else { + mainView.findViewById(R.id.plugin_image_placeholder).setVisibility(View.VISIBLE); + } + + TextView descriptionView = mainView.findViewById(R.id.plugin_description); + descriptionView.setText(plugin.getDescription()); + + int linkTextColorId = nightMode ? R.color.ctx_menu_bottom_view_url_color_dark : R.color.ctx_menu_bottom_view_url_color_light; + int linkTextColor = ContextCompat.getColor(context, linkTextColorId); + + descriptionView.setLinkTextColor(linkTextColor); + descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); + AndroidUtils.removeLinkUnderline(descriptionView); + + Button settingsButton = mainView.findViewById(R.id.plugin_settings); + settingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + FragmentActivity activity = getActivity(); + if (activity != null) { + SettingsScreenType settingsScreenType = plugin.getSettingsScreenType(); + if (settingsScreenType != null) { + BaseSettingsFragment.showInstance(activity, settingsScreenType); + } + } + } + }); + + CompoundButton enableDisableButton = mainView.findViewById(R.id.plugin_enable_disable); + enableDisableButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (plugin.isActive() == isChecked) { + return; + } + + boolean ok = OsmandPlugin.enablePlugin(getActivity(), app, plugin, isChecked); + if (!ok) { + return; + } + updateState(); + } + }); + Button getButton = mainView.findViewById(R.id.plugin_get); + getButton.setText(plugin.isPaid() ? R.string.get_plugin : R.string.shared_string_install); + getButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + if (plugin instanceof SRTMPlugin) { + FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); + if (fragmentManager != null) { + ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(fragmentManager); + } + } else { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); + } + } catch (Exception e) { + //ignored + } + } + }); + + updateState(); + return mainView; + } + + @Override + public void onResume() { + super.onResume(); + OsmandPlugin.checkInstalledMarketPlugins(app, getActivity()); + updateState(); + } + + private void updateState() { + CompoundButton enableDisableButton = mainView.findViewById(R.id.plugin_enable_disable); + Button getButton = mainView.findViewById(R.id.plugin_get); + Button settingsButton = mainView.findViewById(R.id.plugin_settings); + settingsButton.setCompoundDrawablesWithIntrinsicBounds(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_settings), null, null, null); + View installHeader = mainView.findViewById(R.id.plugin_install_header); + + if (plugin.needsInstallation()) { + getButton.setVisibility(View.VISIBLE); + enableDisableButton.setVisibility(View.GONE); + settingsButton.setVisibility(View.GONE); + installHeader.setVisibility(View.VISIBLE); + View worldGlobeIcon = installHeader.findViewById(R.id.ic_world_globe); + Drawable worldGlobeDrawable = app.getUIUtilities().getThemedIcon(R.drawable.ic_world_globe_dark); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + worldGlobeIcon.setBackground(worldGlobeDrawable); + } else { + worldGlobeIcon.setBackgroundDrawable(worldGlobeDrawable); + } + } else { + getButton.setVisibility(View.GONE); + enableDisableButton.setVisibility(View.VISIBLE); + enableDisableButton.setChecked(plugin.isActive()); + + if (plugin.getSettingsScreenType() == null || !plugin.isActive()) { + settingsButton.setVisibility(View.GONE); + } else { + settingsButton.setVisibility(View.VISIBLE); + } + installHeader.setVisibility(View.GONE); + } + } + + @Override + public void onPluginStateChanged(OsmandPlugin plugin) { + updateState(); + } + + public void dismiss() { + FragmentActivity activity = getActivity(); + if (activity != null) { + try { + activity.getSupportFragmentManager().popBackStack(TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + } catch (Exception e) { + log.error(e); + } + } + } + + public static boolean showInstance(FragmentManager fragmentManager, OsmandPlugin plugin) { + try { + Bundle args = new Bundle(); + args.putString(EXTRA_PLUGIN_ID, plugin.getId()); + + PluginInfoFragment fragment = new PluginInfoFragment(); + fragment.setArguments(args); + fragmentManager.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG) + .commitAllowingStateLoss(); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java b/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java new file mode 100644 index 0000000000..783e7791c5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/PluginsFragment.java @@ -0,0 +1,338 @@ +package net.osmand.plus.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.aidl.ConnectedApp; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog.PluginStateListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; + +public class PluginsFragment extends BaseOsmAndFragment implements PluginStateListener { + + private static final Log log = PlatformUtil.getLog(PluginsFragment.class); + + public static final String TAG = PluginsFragment.class.getName(); + + public static final String OPEN_PLUGINS = "open_plugins"; + + private OsmandApplication app; + private PluginsListAdapter adapter; + + private LayoutInflater themedInflater; + private boolean nightMode; + + @Override + public int getStatusBarColorId() { + return nightMode ? R.color.status_bar_color_dark : R.color.status_bar_color_light; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentActivity activity = requireMyActivity(); + activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + FragmentActivity activity = getActivity(); + if (activity instanceof MapActivity) { + dismissImmediate(); + MapActivity mapActivity = (MapActivity) activity; + mapActivity.launchPrevActivityIntent(); + } + } + }); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + app = requireMyApplication(); + nightMode = !app.getSettings().isLightContent(); + + themedInflater = UiUtilities.getInflater(getContext(), nightMode); + View view = themedInflater.inflate(R.layout.plugins, container, false); + AndroidUtils.addStatusBarPadding21v(getContext(), view); + + TextView toolbarTitle = view.findViewById(R.id.toolbar_title); + toolbarTitle.setText(R.string.plugins_screen); + + ImageView closeButton = view.findViewById(R.id.close_button); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Activity activity = getMyActivity(); + if (activity != null) { + activity.onBackPressed(); + } + } + }); + UiUtilities.rotateImageByLayoutDirection(closeButton, AndroidUtils.getLayoutDirection(app)); + + adapter = new PluginsListAdapter(requireContext()); + + ListView listView = view.findViewById(R.id.plugins_list); + listView.setAdapter(adapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Object tag = view.getTag(); + if (tag instanceof OsmandPlugin) { + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginInfoFragment.showInstance(activity.getSupportFragmentManager(), (OsmandPlugin) tag); + } + } else if (tag instanceof ConnectedApp) { + switchEnabled((ConnectedApp) tag); + } + } + }); + return view; + } + + @Override + public void onResume() { + super.onResume(); + OsmandPlugin.checkInstalledMarketPlugins(app, getActivity()); + adapter.notifyDataSetChanged(); + } + + private void enableDisablePlugin(OsmandPlugin plugin, boolean enable) { + if (OsmandPlugin.enablePlugin(getActivity(), app, plugin, enable)) { + adapter.notifyDataSetChanged(); + } + } + + private void switchEnabled(@NonNull ConnectedApp connectedApp) { + app.getAidlApi().switchEnabled(connectedApp); + adapter.notifyDataSetChanged(); + } + + @Override + public void onPluginStateChanged(OsmandPlugin plugin) { + adapter.notifyDataSetChanged(); + } + + protected class PluginsListAdapter extends ArrayAdapter { + + PluginsListAdapter(Context context) { + super(context, R.layout.plugins_list_item, new ArrayList<>()); + addAll(app.getAidlApi().getConnectedApps()); + addAll(OsmandPlugin.getVisiblePlugins()); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View view = convertView; + if (view == null) { + view = themedInflater.inflate(R.layout.plugins_list_item, parent, false); + } + Context context = view.getContext(); + + boolean active = false; + int logoContDescId = R.string.shared_string_disable; + String name = ""; + + ImageButton pluginLogo = view.findViewById(R.id.plugin_logo); + ImageView pluginOptions = view.findViewById(R.id.plugin_options); + TextView pluginDescription = view.findViewById(R.id.plugin_description); + + Object item = getItem(position); + if (item instanceof ConnectedApp) { + final ConnectedApp app = (ConnectedApp) item; + active = app.isEnabled(); + if (!active) { + logoContDescId = R.string.shared_string_enable; + } + name = app.getName(); + pluginDescription.setText(R.string.third_party_application); + pluginLogo.setImageDrawable(app.getIcon()); + pluginLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchEnabled(app); + } + }); + pluginOptions.setVisibility(View.GONE); + pluginOptions.setOnClickListener(null); + view.setTag(app); + } else if (item instanceof OsmandPlugin) { + final OsmandPlugin plugin = (OsmandPlugin) item; + active = plugin.isActive(); + if (!active) { + logoContDescId = plugin.needsInstallation() + ? R.string.access_shared_string_not_installed : R.string.shared_string_enable; + } + name = plugin.getName(); + pluginDescription.setText(plugin.getDescription()); + + int linkTextColorId = nightMode ? R.color.ctx_menu_bottom_view_url_color_dark : R.color.ctx_menu_bottom_view_url_color_light; + int linkTextColor = ContextCompat.getColor(context, linkTextColorId); + + pluginDescription.setLinkTextColor(linkTextColor); + pluginDescription.setMovementMethod(LinkMovementMethod.getInstance()); + AndroidUtils.removeLinkUnderline(pluginDescription); + + int color = AndroidUtils.getColorFromAttr(context, R.attr.list_background_color); + pluginLogo.setImageDrawable(UiUtilities.tintDrawable(plugin.getLogoResource(), color)); + pluginLogo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (plugin.isActive() || !plugin.needsInstallation()) { + enableDisablePlugin(plugin, !plugin.isActive()); + } + } + }); + pluginOptions.setVisibility(View.VISIBLE); + pluginOptions.setImageDrawable(app.getUIUtilities().getThemedIcon(R.drawable.ic_overflow_menu_white)); + pluginOptions.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showOptionsMenu(v, plugin); + } + }); + view.setTag(plugin); + } + + pluginLogo.setContentDescription(getString(logoContDescId)); + if (active) { + pluginLogo.setBackgroundResource(nightMode ? R.drawable.bg_plugin_logo_enabled_dark : R.drawable.bg_plugin_logo_enabled_light); + } else { + TypedArray attributes = context.getTheme().obtainStyledAttributes(new int[] {R.attr.bg_plugin_logo_disabled}); + pluginLogo.setBackgroundDrawable(attributes.getDrawable(0)); + attributes.recycle(); + } + + TextView pluginName = view.findViewById(R.id.plugin_name); + pluginName.setText(name); + pluginName.setContentDescription(name + " " + getString(active + ? R.string.item_checked + : R.string.item_unchecked)); + + return view; + } + } + + private void showOptionsMenu(View view, final OsmandPlugin plugin) { + final PopupMenu optionsMenu = new PopupMenu(view.getContext(), view); + if (plugin.isActive() || !plugin.needsInstallation()) { + MenuItem enableDisableItem = optionsMenu.getMenu().add( + plugin.isActive() ? R.string.shared_string_disable + : R.string.shared_string_enable); + enableDisableItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + enableDisablePlugin(plugin, !plugin.isActive()); + optionsMenu.dismiss(); + return true; + } + }); + } + + final SettingsScreenType settingsScreenType = plugin.getSettingsScreenType(); + if (settingsScreenType != null && plugin.isActive()) { + MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_settings); + settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + FragmentActivity activity = getActivity(); + if (activity != null) { + BaseSettingsFragment.showInstance(activity, settingsScreenType); + } + optionsMenu.dismiss(); + return true; + } + }); + } + + if (plugin instanceof CustomOsmandPlugin) { + MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_delete); + settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + showDeletePluginDialog((CustomOsmandPlugin) plugin); + optionsMenu.dismiss(); + return true; + } + }); + } + + optionsMenu.show(); + } + + private void showDeletePluginDialog(final CustomOsmandPlugin plugin) { + Context context = getContext(); + if (context != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(getString(R.string.delete_confirmation_msg, plugin.getName())); + builder.setMessage(R.string.are_you_sure); + builder.setNegativeButton(R.string.shared_string_cancel, null); + builder.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + OsmandPlugin.removeCustomPlugin(app, plugin); + adapter.remove(plugin); + } + }); + builder.show(); + } + } + + public void dismissImmediate() { + FragmentActivity activity = getActivity(); + if (activity != null) { + try { + activity.getSupportFragmentManager().popBackStackImmediate(TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); + } catch (Exception e) { + log.error(e); + } + } + } + + public static boolean showInstance(FragmentManager fragmentManager) { + try { + PluginsFragment fragment = new PluginsFragment(); + fragmentManager.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG) + .commitAllowingStateLoss(); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java index 17559f0e9d..445af63f70 100644 --- a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java @@ -77,15 +77,6 @@ public class SettingsActivity extends SettingsBaseActivity { } } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if ((requestCode == PLUGINS_SELECTION_REQUEST) && (resultCode == PluginsActivity.ACTIVE_PLUGINS_LIST_MODIFIED)) { - finish(); - startActivity(getIntent()); - } - } - @Override public boolean onPreferenceClick(Preference preference) { if (preference == general) { diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java index 20affaa27a..e8e944f947 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java @@ -14,11 +14,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.PluginActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; import net.osmand.plus.dashboard.tools.DashFragmentData; import net.osmand.plus.development.OsmandDevelopmentPlugin; @@ -84,7 +86,10 @@ public class DashPluginsFragment extends DashBaseFragment { view.findViewById(R.id.show_all).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - startActivity(new Intent(getActivity(), getMyApplication().getAppCustomization().getPluginsActivity())); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } closeDashboard(); } }); diff --git a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java index ec1c70fd41..e4cdc5ce94 100644 --- a/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java +++ b/OsmAnd/src/net/osmand/plus/download/ui/ItemViewHolder.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.os.Bundle; import android.util.TypedValue; import android.view.MenuItem; import android.view.View; @@ -25,6 +26,8 @@ import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType; import net.osmand.plus.activities.LocalIndexInfo; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; import net.osmand.plus.download.CityItem; import net.osmand.plus.download.CustomIndexItem; @@ -343,8 +346,7 @@ public class ItemViewHolder { ChoosePlanDialogFragment.showSeaDepthMapsInstance(context.getSupportFragmentManager()); break; case ASK_FOR_SEAMARKS_PLUGIN: - context.startActivity(new Intent(context, context.getMyApplication().getAppCustomization() - .getPluginsActivity())); + showPluginsScreen(); Toast.makeText(context.getApplicationContext(), context.getString(R.string.activate_seamarks_plugin), Toast.LENGTH_SHORT).show(); break; @@ -352,8 +354,7 @@ public class ItemViewHolder { ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(context.getSupportFragmentManager()); break; case ASK_FOR_SRTM_PLUGIN_ENABLE: - context.startActivity(new Intent(context, context.getMyApplication().getAppCustomization() - .getPluginsActivity())); + showPluginsScreen(); Toast.makeText(context, context.getString(R.string.activate_srtm_plugin), Toast.LENGTH_SHORT).show(); break; @@ -361,6 +362,13 @@ public class ItemViewHolder { break; } } + + private void showPluginsScreen() { + Bundle params = new Bundle(); + params.putBoolean(PluginsFragment.OPEN_PLUGINS, true); + Intent intent = context.getIntent(); + MapActivity.launchMapActivityMoveToTop(context, intent != null ? intent.getExtras() : null, null, params); + } }; } else { final boolean isDownloading = context.getDownloadThread().isDownloading(item); diff --git a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java index d58271a27c..161ef5ccde 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java @@ -11,16 +11,17 @@ import net.osmand.PlatformUtil; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.map.TileSourceManager; +import net.osmand.plus.MapMarkersHelper; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; +import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; +import net.osmand.plus.mapmarkers.MapMarkersDialogFragment; import net.osmand.plus.mapsource.EditMapSourceDialogFragment; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.MapMarkersHelper; -import net.osmand.plus.OsmandApplication; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.R; -import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; -import net.osmand.plus.mapmarkers.MapMarkersDialogFragment; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.util.Algorithms; @@ -207,10 +208,22 @@ public class IntentHelper { mapActivity.setIntent(null); } if (intent.hasExtra(BaseSettingsFragment.OPEN_SETTINGS)) { - String settingsType = intent.getStringExtra(BaseSettingsFragment.OPEN_SETTINGS); String appMode = intent.getStringExtra(BaseSettingsFragment.APP_MODE_KEY); - if (BaseSettingsFragment.OPEN_CONFIG_PROFILE.equals(settingsType)) { - BaseSettingsFragment.showInstance(mapActivity, SettingsScreenType.CONFIGURE_PROFILE, ApplicationMode.valueOfStringKey(appMode, null)); + String settingsTypeName = intent.getStringExtra(BaseSettingsFragment.OPEN_SETTINGS); + if (!Algorithms.isEmpty(settingsTypeName)) { + try { + SettingsScreenType screenType = SettingsScreenType.valueOf(settingsTypeName); + BaseSettingsFragment.showInstance(mapActivity, screenType, ApplicationMode.valueOfStringKey(appMode, null)); + } catch (IllegalArgumentException e) { + LOG.error("error", e); + } + } + mapActivity.setIntent(null); + } + if (intent.hasExtra(PluginsFragment.OPEN_PLUGINS)) { + boolean openPlugins = intent.getBooleanExtra(PluginsFragment.OPEN_PLUGINS, false); + if (openPlugins) { + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); } mapActivity.setIntent(null); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java index 272975b987..ac6b8287b4 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/MapDataMenuController.java @@ -22,6 +22,7 @@ import net.osmand.plus.activities.LocalIndexHelper; import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType; import net.osmand.plus.activities.LocalIndexInfo; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.download.DownloadActivityType; import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadValidationManager; @@ -110,8 +111,7 @@ public class MapDataMenuController extends MenuController { activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show(); } } else { - activity.startActivity(new Intent(activity, activity.getMyApplication().getAppCustomization() - .getPluginsActivity())); + PluginsFragment.showInstance(activity.getSupportFragmentManager()); Toast.makeText(activity, activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_SHORT).show(); } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java index c8025bc2d5..9555fdaf14 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmAndAppCustomization.java @@ -27,12 +27,11 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.Version; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.activities.PluginsActivity; import net.osmand.plus.activities.SettingsActivity; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.download.DownloadActivity; -import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.helpers.WaypointHelper; +import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.routing.RouteCalculationResult; import net.osmand.plus.views.OsmandMapTileView; @@ -184,10 +183,6 @@ public class OsmAndAppCustomization { return DownloadActivity.class; } - public Class getPluginsActivity() { - return PluginsActivity.class; - } - public Class getDownloadActivity() { return DownloadActivity.class; } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java index 330f1c0a18..beb1711f31 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java @@ -1,7 +1,6 @@ package net.osmand.plus.settings.fragments; import android.app.Activity; -import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -22,13 +21,13 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.OsmandApplication; @@ -36,14 +35,14 @@ import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivityActions; -import net.osmand.plus.activities.PluginsActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.base.BaseOsmAndFragment; import net.osmand.plus.dialogs.ConfigureMapMenu; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.MapContextMenu; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.widgets.style.CustomTypefaceSpan; - import org.apache.commons.logging.Log; import java.util.ArrayList; @@ -229,8 +228,7 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { if (holder instanceof DescriptionHolder) { DescriptionHolder descriptionHolder = (DescriptionHolder) holder; String plugins = getString(R.string.prefs_plugins); - setupClickableText( - descriptionHolder.description, (String) currentItem, plugins, new Intent(app, PluginsActivity.class)); + setupClickableText(descriptionHolder.description, (String) currentItem, plugins); descriptionHolder.image.setVisibility(View.GONE); } else { final ScreenType item = (ScreenType) currentItem; @@ -253,12 +251,15 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { return items.size(); } - private void setupClickableText(TextView textView, String text, String clickableText, final Intent intent) { + private void setupClickableText(TextView textView, String text, String clickableText) { SpannableString spannableString = new SpannableString(text); ClickableSpan clickableSpan = new ClickableSpan() { @Override public void onClick(@NonNull View view) { - startActivity(intent); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } } }; try { From 3b81b1d2c735ab7c6c01e892d212f1a22878f012 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 12 Oct 2020 22:05:31 +0300 Subject: [PATCH 011/123] Remove outdated plugin screens from plugins --- OsmAnd/AndroidManifest.xml | 2 - OsmAnd/res/layout/plugin.xml | 3 +- .../osmand/access/AccessibilityPlugin.java | 5 - OsmAnd/src/net/osmand/plus/OsmandPlugin.java | 4 - .../plus/activities/PluginActivity.java | 241 -------------- .../plus/activities/PluginsActivity.java | 296 ------------------ .../plus/activities/SettingsActivity.java | 32 +- .../audionotes/AudioVideoNotesPlugin.java | 5 - .../plus/dashboard/DashPluginsFragment.java | 8 +- .../development/OsmandDevelopmentPlugin.java | 5 - .../plus/dialogs/MapLayerMenuListener.java | 7 +- .../net/osmand/plus/helpers/GpxUiHelper.java | 9 +- .../monitoring/OsmandMonitoringPlugin.java | 6 - .../osmand/plus/osmedit/OsmEditingPlugin.java | 5 - 14 files changed, 27 insertions(+), 601 deletions(-) delete mode 100644 OsmAnd/src/net/osmand/plus/activities/PluginActivity.java delete mode 100644 OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 4e5395a04a..9de6d31998 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -497,8 +497,6 @@ - - diff --git a/OsmAnd/res/layout/plugin.xml b/OsmAnd/res/layout/plugin.xml index 710015345c..056fc218f0 100644 --- a/OsmAnd/res/layout/plugin.xml +++ b/OsmAnd/res/layout/plugin.xml @@ -23,8 +23,7 @@ + android:orientation="vertical" > getSettingsActivity() { - return SettingsAccessibilityActivity.class; - } - @Override public SettingsScreenType getSettingsScreenType() { return SettingsScreenType.ACCESSIBILITY_SETTINGS; diff --git a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java index 8b3326cf02..4198f7036a 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java @@ -111,10 +111,6 @@ public abstract class OsmandPlugin { return app.getUIUtilities().getIcon(getLogoResourceId()); } - public Class getSettingsActivity() { - return null; - } - public SettingsScreenType getSettingsScreenType() { return null; } diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java b/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java deleted file mode 100644 index e367f3cd8e..0000000000 --- a/OsmAnd/src/net/osmand/plus/activities/PluginActivity.java +++ /dev/null @@ -1,241 +0,0 @@ -package net.osmand.plus.activities; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.appcompat.content.res.AppCompatResources; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - -import net.osmand.AndroidUtils; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; -import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog; -import net.osmand.plus.download.DownloadIndexesThread; -import net.osmand.plus.srtmplugin.SRTMPlugin; - -public class PluginActivity extends OsmandActionBarActivity implements DownloadIndexesThread.DownloadEvents, PluginInstalledBottomSheetDialog.PluginStateListener { - private static final String TAG = "PluginActivity"; - public static final String EXTRA_PLUGIN_ID = "plugin_id"; - - private OsmandPlugin plugin; - - @Override - protected void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - if (intent == null || !intent.hasExtra(EXTRA_PLUGIN_ID)) { - Log.e(TAG, "Required extra '" + EXTRA_PLUGIN_ID + "' is missing"); - finish(); - return; - } - String pluginId = intent.getStringExtra(EXTRA_PLUGIN_ID); - if (pluginId == null) { - Log.e(TAG, "Extra '" + EXTRA_PLUGIN_ID + "' is null"); - finish(); - return; - } - for (OsmandPlugin plugin : OsmandPlugin.getAvailablePlugins()) { - if (!plugin.getId().equals(pluginId)) - continue; - - this.plugin = plugin; - break; - } - if (plugin == null) { - Log.e(TAG, "Plugin '" + EXTRA_PLUGIN_ID + "' not found"); - finish(); - return; - } - - setContentView(R.layout.plugin); - //noinspection ConstantConditions - getSupportActionBar().setTitle(plugin.getName()); - Drawable pluginImage = plugin.getAssetResourceImage(); - if (pluginImage != null) { - ImageView img = (ImageView) findViewById(R.id.plugin_image); - img.setImageDrawable(pluginImage); - } else { - findViewById(R.id.plugin_image_placeholder).setVisibility(View.VISIBLE); - } - - TextView descriptionView = (TextView) findViewById(R.id.plugin_description); - descriptionView.setText(plugin.getDescription()); - - boolean light = getMyApplication().getSettings().isLightContent(); - int linkTextColor = ContextCompat.getColor(this, - light ? R.color.ctx_menu_bottom_view_url_color_light : R.color.ctx_menu_bottom_view_url_color_dark); - - descriptionView.setLinkTextColor(linkTextColor); - descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); - AndroidUtils.removeLinkUnderline(descriptionView); - - Button settingsButton = (Button) findViewById(R.id.plugin_settings); - settingsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(PluginActivity.this, plugin.getSettingsActivity())); - } - }); - - CompoundButton enableDisableButton = (CompoundButton)findViewById( - R.id.plugin_enable_disable); - enableDisableButton.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (plugin.isActive() == isChecked) { - return; - } - - boolean ok = OsmandPlugin.enablePlugin(PluginActivity.this, (OsmandApplication)getApplication(), - plugin, isChecked); - if (!ok) { - return; - } - updateState(); - } - }); - Button getButton = (Button)findViewById(R.id.plugin_get); - getButton.setText(plugin.isPaid() ? R.string.get_plugin : R.string.shared_string_install); - getButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - if (plugin instanceof SRTMPlugin) { - FragmentManager fragmentManager = getSupportFragmentManager(); - if (fragmentManager != null) { - ChoosePlanDialogFragment.showHillshadeSrtmPluginInstance(fragmentManager); - } - } else { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); - } - } catch (Exception e) { - //ignored - } - } - }); - - updateState(); - } - - @Override - protected void onResume() { - super.onResume(); - OsmandApplication app = getMyApplication(); - OsmandPlugin.checkInstalledMarketPlugins(app, this); - app.getDownloadThread().setUiActivity(this); - updateState(); - } - - @Override - protected void onPause() { - super.onPause(); - getMyApplication().getDownloadThread().resetUiActivity(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); - switch (itemId) { - case android.R.id.home: - finish(); - return true; - - } - return false; - } - - @SuppressLint("NewApi") - private void updateState() { - CompoundButton enableDisableButton = (CompoundButton)findViewById( - R.id.plugin_enable_disable); - Button getButton = (Button)findViewById(R.id.plugin_get); - Button settingsButton = (Button)findViewById(R.id.plugin_settings); - settingsButton.setCompoundDrawablesWithIntrinsicBounds( - getMyApplication().getUIUtilities().getThemedIcon(R.drawable.ic_action_settings), - null, null, null); - View installHeader = findViewById(R.id.plugin_install_header); - - if (plugin.needsInstallation()) { - getButton.setVisibility(View.VISIBLE); - enableDisableButton.setVisibility(View.GONE); - settingsButton.setVisibility(View.GONE); - installHeader.setVisibility(View.VISIBLE); - View worldGlobeIcon = installHeader.findViewById(R.id.ic_world_globe); - Drawable worldGlobeDrawable = getMyApplication().getUIUtilities().getThemedIcon( - R.drawable.ic_world_globe_dark); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - worldGlobeIcon.setBackground(worldGlobeDrawable); - } else { - //noinspection deprecation - worldGlobeIcon.setBackgroundDrawable(worldGlobeDrawable); - } - } else { - getButton.setVisibility(View.GONE); - enableDisableButton.setVisibility(View.VISIBLE); - enableDisableButton.setChecked(plugin.isActive()); - - final Class settingsActivity = plugin.getSettingsActivity(); - if (settingsActivity == null || !plugin.isActive()) { - settingsButton.setVisibility(View.GONE); - } else { - settingsButton.setVisibility(View.VISIBLE); - } - - installHeader.setVisibility(View.GONE); - } - } - - // DownloadEvents - @Override - public void newDownloadIndexes() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).newDownloadIndexes(); - } - } - } - - @Override - public void downloadInProgress() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadInProgress(); - } - } - } - - @Override - public void downloadHasFinished() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadHasFinished(); - } - } - } - - @Override - public void onPluginStateChanged(OsmandPlugin plugin) { - updateState(); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java b/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java deleted file mode 100644 index a0e731c7bc..0000000000 --- a/OsmAnd/src/net/osmand/plus/activities/PluginsActivity.java +++ /dev/null @@ -1,296 +0,0 @@ -package net.osmand.plus.activities; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.TypedArray; -import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - -import net.osmand.AndroidUtils; -import net.osmand.aidl.ConnectedApp; -import net.osmand.plus.CustomOsmandPlugin; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.dialogs.PluginInstalledBottomSheetDialog; -import net.osmand.plus.download.DownloadIndexesThread; - -import java.util.ArrayList; - -public class PluginsActivity extends OsmandListActivity implements DownloadIndexesThread.DownloadEvents, PluginInstalledBottomSheetDialog.PluginStateListener { - - public static final int ACTIVE_PLUGINS_LIST_MODIFIED = 1; - - private boolean listModified = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getMyApplication().applyTheme(this); - super.onCreate(savedInstanceState); - setContentView(R.layout.plugins); - getSupportActionBar().setTitle(R.string.plugins_screen); - setListAdapter(new PluginsListAdapter()); - } - - @Override - public PluginsListAdapter getListAdapter() { - return (PluginsListAdapter) super.getListAdapter(); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Object tag = view.getTag(); - if (tag instanceof OsmandPlugin) { - Intent intent = new Intent(this, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, ((OsmandPlugin) tag).getId()); - startActivity(intent); - } else if (tag instanceof ConnectedApp) { - switchEnabled((ConnectedApp) tag); - } - } - - @Override - protected void onResume() { - super.onResume(); - OsmandApplication app = getMyApplication(); - OsmandPlugin.checkInstalledMarketPlugins(app, this); - app.getDownloadThread().setUiActivity(this); - getListAdapter().notifyDataSetChanged(); - } - - @Override - protected void onPause() { - super.onPause(); - getMyApplication().getDownloadThread().resetUiActivity(this); - } - - private void enableDisablePlugin(OsmandPlugin plugin, boolean enable) { - OsmandApplication app = getMyApplication(); - if (OsmandPlugin.enablePlugin(this, app, plugin, enable)) { - if (!listModified) { - setResult(ACTIVE_PLUGINS_LIST_MODIFIED); - listModified = true; - } - getListAdapter().notifyDataSetChanged(); - } - } - - private void switchEnabled(@NonNull ConnectedApp app) { - getMyApplication().getAidlApi().switchEnabled(app); - getListAdapter().notifyDataSetChanged(); - } - - // DownloadEvents - @Override - public void newDownloadIndexes() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).newDownloadIndexes(); - } - } - } - - @Override - public void downloadInProgress() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadInProgress(); - } - } - } - - @Override - public void downloadHasFinished() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment instanceof DownloadIndexesThread.DownloadEvents && fragment.isAdded()) { - ((DownloadIndexesThread.DownloadEvents) fragment).downloadHasFinished(); - } - } - } - - @Override - public void onPluginStateChanged(OsmandPlugin plugin) { - getListAdapter().notifyDataSetChanged(); - } - - protected class PluginsListAdapter extends ArrayAdapter { - PluginsListAdapter() { - super(PluginsActivity.this, R.layout.plugins_list_item, new ArrayList<>()); - addAll(getMyApplication().getAidlApi().getConnectedApps()); - addAll(OsmandPlugin.getVisiblePlugins()); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View view = convertView; - if (view == null) { - view = getLayoutInflater().inflate(R.layout.plugins_list_item, parent, false); - } - - final Object item = getItem(position); - - boolean active = false; - int logoContDescId = R.string.shared_string_disable; - String name = ""; - boolean isLightTheme = getMyApplication().getSettings().isLightContent(); - - ImageButton pluginLogo = (ImageButton) view.findViewById(R.id.plugin_logo); - ImageView pluginOptions = (ImageView) view.findViewById(R.id.plugin_options); - TextView pluginDescription = (TextView) view.findViewById(R.id.plugin_description); - - if (item instanceof ConnectedApp) { - final ConnectedApp app = (ConnectedApp) item; - active = app.isEnabled(); - if (!active) { - logoContDescId = R.string.shared_string_enable; - } - name = app.getName(); - pluginDescription.setText(R.string.third_party_application); - pluginLogo.setImageDrawable(app.getIcon()); - pluginLogo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - switchEnabled(app); - } - }); - pluginOptions.setVisibility(View.GONE); - pluginOptions.setOnClickListener(null); - view.setTag(app); - } else if (item instanceof OsmandPlugin) { - final OsmandPlugin plugin = (OsmandPlugin) item; - active = plugin.isActive(); - if (!active) { - logoContDescId = plugin.needsInstallation() - ? R.string.access_shared_string_not_installed : R.string.shared_string_enable; - } - name = plugin.getName(); - pluginDescription.setText(plugin.getDescription()); - - boolean light = getMyApplication().getSettings().isLightContent(); - int linkTextColor = ContextCompat.getColor(PluginsActivity.this, - light ? R.color.ctx_menu_bottom_view_url_color_light : R.color.ctx_menu_bottom_view_url_color_dark); - - pluginDescription.setLinkTextColor(linkTextColor); - pluginDescription.setMovementMethod(LinkMovementMethod.getInstance()); - AndroidUtils.removeLinkUnderline(pluginDescription); - - OsmandApplication app = getMyApplication(); - int color = AndroidUtils.getColorFromAttr(PluginsActivity.this, R.attr.list_background_color); - pluginLogo.setImageDrawable(UiUtilities.tintDrawable(plugin.getLogoResource(), color)); - pluginLogo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (plugin.isActive() || !plugin.needsInstallation()) { - enableDisablePlugin(plugin, !plugin.isActive()); - } - } - }); - pluginOptions.setVisibility(View.VISIBLE); - pluginOptions.setImageDrawable(getMyApplication().getUIUtilities().getThemedIcon(R.drawable.ic_overflow_menu_white)); - pluginOptions.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showOptionsMenu(v, plugin); - } - }); - view.setTag(plugin); - } - - pluginLogo.setContentDescription(getString(logoContDescId)); - if (active) { - pluginLogo.setBackgroundResource(isLightTheme ? R.drawable.bg_plugin_logo_enabled_light : R.drawable.bg_plugin_logo_enabled_dark); - } else { - TypedArray attributes = getTheme().obtainStyledAttributes(new int[] {R.attr.bg_plugin_logo_disabled}); - pluginLogo.setBackgroundDrawable(attributes.getDrawable(0)); - attributes.recycle(); - } - - TextView pluginName = (TextView) view.findViewById(R.id.plugin_name); - pluginName.setText(name); - pluginName.setContentDescription(name + " " + getString(active - ? R.string.item_checked - : R.string.item_unchecked)); - - return view; - } - } - - private void showOptionsMenu(View v, final OsmandPlugin plugin) { - final Class settingsActivity = plugin.getSettingsActivity(); - - final PopupMenu optionsMenu = new PopupMenu(this, v); - if (plugin.isActive() || !plugin.needsInstallation()) { - MenuItem enableDisableItem = optionsMenu.getMenu().add( - plugin.isActive() ? R.string.shared_string_disable - : R.string.shared_string_enable); - enableDisableItem - .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - enableDisablePlugin(plugin, !plugin.isActive()); - optionsMenu.dismiss(); - return true; - } - }); - } - - if (settingsActivity != null && plugin.isActive()) { - MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_settings); - settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(PluginsActivity.this, settingsActivity)); - optionsMenu.dismiss(); - return true; - } - }); - } - - if (plugin instanceof CustomOsmandPlugin) { - MenuItem settingsItem = optionsMenu.getMenu().add(R.string.shared_string_delete); - settingsItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - showDeletePluginDialog((CustomOsmandPlugin) plugin); - optionsMenu.dismiss(); - return true; - } - }); - } - - optionsMenu.show(); - } - - private void showDeletePluginDialog(final CustomOsmandPlugin plugin) { - AlertDialog.Builder builder = new AlertDialog.Builder(PluginsActivity.this); - builder.setTitle(getString(R.string.delete_confirmation_msg, plugin.getName())); - builder.setMessage(R.string.are_you_sure); - builder.setNegativeButton(R.string.shared_string_cancel, null); - builder.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - OsmandApplication app = getMyApplication(); - OsmandPlugin.removeCustomPlugin(app, plugin); - getListAdapter().remove(plugin); - } - }); - builder.show(); - } -} diff --git a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java index 445af63f70..3509f99d65 100644 --- a/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/SettingsActivity.java @@ -1,11 +1,9 @@ package net.osmand.plus.activities; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; @@ -59,21 +57,21 @@ public class SettingsActivity extends SettingsBaseActivity { } PreferenceCategory plugins = (PreferenceCategory) screen.findPreference("plugin_settings"); for(OsmandPlugin op : OsmandPlugin.getEnabledPlugins()) { - final Class sa = op.getSettingsActivity(); - if(sa != null) { - Preference preference = new Preference(this); - preference.setTitle(op.getName()); - preference.setKey(op.getId()); - preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsActivity.this, sa)); - return false; - } - }); - plugins.addPreference(preference); - } +// final Class sa = op.getSettingsActivity(); +// if(sa != null) { +// Preference preference = new Preference(this); +// preference.setTitle(op.getName()); +// preference.setKey(op.getId()); +// preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { +// +// @Override +// public boolean onPreferenceClick(Preference preference) { +// startActivity(new Intent(SettingsActivity.this, sa)); +// return false; +// } +// }); +// plugins.addPreference(preference); +// } } } diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java index fe023e9dd7..839c1b26f0 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java @@ -1799,11 +1799,6 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } } - @Override - public Class getSettingsActivity() { - return SettingsAudioVideoActivity.class; - } - @Override public SettingsScreenType getSettingsScreenType() { return SettingsScreenType.MULTIMEDIA_NOTES; diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java index e8e944f947..15d222a980 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashPluginsFragment.java @@ -19,7 +19,6 @@ import androidx.fragment.app.FragmentManager; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.activities.PluginActivity; import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.chooseplan.ChoosePlanDialogFragment; import net.osmand.plus.dashboard.tools.DashFragmentData; @@ -70,9 +69,10 @@ public class DashPluginsFragment extends DashBaseFragment { return new View.OnClickListener() { @Override public void onClick(View view) { - Intent intent = new Intent(getActivity(), PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, plugin.getId()); - startActivity(intent); + FragmentActivity activity = getActivity(); + if (activity != null) { + PluginsFragment.showInstance(activity.getSupportFragmentManager()); + } closeDashboard(); } }; diff --git a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java index b7f0e0e0f8..d7798a646b 100644 --- a/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java +++ b/OsmAnd/src/net/osmand/plus/development/OsmandDevelopmentPlugin.java @@ -121,11 +121,6 @@ public class OsmandDevelopmentPlugin extends OsmandPlugin { } } - @Override - public Class getSettingsActivity() { - return SettingsDevelopmentActivity.class; - } - @Override public SettingsScreenType getSettingsScreenType() { return SettingsScreenType.DEVELOPMENT_SETTINGS; diff --git a/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java b/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java index 9234239040..29a64bcee4 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/MapLayerMenuListener.java @@ -1,7 +1,6 @@ package net.osmand.plus.dialogs; import android.content.DialogInterface; -import android.content.Intent; import android.view.View; import android.widget.ArrayAdapter; import android.widget.CompoundButton; @@ -20,7 +19,7 @@ import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivityLayers; -import net.osmand.plus.activities.PluginActivity; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.poi.PoiFiltersHelper; import net.osmand.plus.poi.PoiUIFilter; @@ -149,9 +148,7 @@ final class MapLayerMenuListener extends OnRowItemClick { settings.SHOW_MAP_MARKERS.set(isChecked); } else if (itemId == R.string.layer_map) { if (OsmandPlugin.getEnabledPlugin(OsmandRasterMapsPlugin.class) == null) { - Intent intent = new Intent(mapActivity, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, OsmandRasterMapsPlugin.ID); - mapActivity.startActivity(intent); + PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); } else { ContextMenuItem it = adapter.getItem(pos); mapActivity.getMapLayers().selectMapLayer(mapActivity.getMapView(), it, adapter); diff --git a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java index 47650dc2ec..dd7e804b4c 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java @@ -13,6 +13,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.text.SpannableString; import android.text.style.StyleSpan; import android.view.ContextThemeWrapper; @@ -80,6 +81,7 @@ import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.helpers.enums.MetricsConstants; import net.osmand.plus.helpers.enums.SpeedConstants; import net.osmand.plus.settings.backend.CommonPreference; @@ -90,7 +92,6 @@ import net.osmand.plus.Version; import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.ActivityResultListener.OnActivityResultListener; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.activities.PluginActivity; import net.osmand.plus.activities.SettingsActivity; import net.osmand.plus.dialogs.ConfigureMapMenu; import net.osmand.plus.dialogs.GpxAppearanceAdapter; @@ -649,9 +650,9 @@ public class GpxUiHelper { confirm.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(activity, PluginActivity.class); - intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, OsmandMonitoringPlugin.ID); - activity.startActivity(intent); + Bundle params = new Bundle(); + params.putBoolean(PluginsFragment.OPEN_PLUGINS, true); + MapActivity.launchMapActivityMoveToTop(activity, null, null, params); } }); confirm.setNegativeButton(R.string.shared_string_cancel, null); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 088a9bd31e..ec255b1bbc 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -166,12 +166,6 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public static final int[] MINUTES = new int[] {2, 3, 5}; public static final int[] MAX_INTERVAL_TO_SEND_MINUTES = new int[] {1, 2, 5, 10, 15, 20, 30, 60, 90, 2 * 60, 3 * 60, 4 * 60, 6 * 60, 12 * 60, 24 * 60}; - - @Override - public Class getSettingsActivity() { - return SettingsMonitoringActivity.class; - } - @Override public SettingsScreenType getSettingsScreenType() { return SettingsScreenType.MONITORING_SETTINGS; diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java index 70f7cd5764..ea779a93b1 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingPlugin.java @@ -197,11 +197,6 @@ public class OsmEditingPlugin extends OsmandPlugin { return osmBugsLayer; } - @Override - public Class getSettingsActivity() { - return SettingsOsmEditingActivity.class; - } - @Override public SettingsScreenType getSettingsScreenType() { return SettingsScreenType.OPEN_STREET_MAP_EDITING; From 1eac34aeef1885d8e8aa35fd57cddd9600085ff0 Mon Sep 17 00:00:00 2001 From: Andrej Shadura Date: Mon, 12 Oct 2020 14:14:13 +0000 Subject: [PATCH 012/123] Translated using Weblate (Belarusian) Currently translated at 99.7% (3497 of 3505 strings) --- OsmAnd/res/values-be/strings.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-be/strings.xml b/OsmAnd/res/values-be/strings.xml index 6839ec5d24..ce32262235 100644 --- a/OsmAnd/res/values-be/strings.xml +++ b/OsmAnd/res/values-be/strings.xml @@ -1576,7 +1576,7 @@ Абмежаванне па вышыні Пазначыць вышыню транспартнага сродку для разліку маршруту. Разумны пераразлік маршруту - Пераразлічваць толькі пачатак маршруту для доўгіх паездак. + Пераразлічваць толькі пачатак маршруту. Падыходзіць для доўгіх паездак. Выйсці Выключана Афарбоўка па пешаходнаму сімвалу OSMC @@ -3957,4 +3957,18 @@ Апошняя змена Назва: Я — А Назва: А — Я + Што новага + Дзякуй за набыццё плагіну «Контурныя лініі» + Плата за падпіску спаганяецца за абраны перыяд. Скасаваць яе на AppGallery можна у любы момант. + Пры пацвярджэнні пакупкі аплата будзе спаганяцца з рахунка, звязанага з вашым акаўнтам AppGallery. +\n +\nПадпіска аўтаматычна працягваецца, калі вы не скасуеце яе да даты працягу. З вашага рахунка будзе адзін раз спаганяцца аплата за перыяд працягу (месяц/тры месяцы/год). +\n +\nВы можаце кіраваць падпіскамі і скасоўваць іх у наладах AppGallery. + Пазбягаць пешаходныя дарожкі + Пазбягаць пешаходныя дарожкі + Распрацоўка + Дзвюхфазная аўтанавігацыя. + Натыўны грамадскі транспарт (у распрацоўцы) + Увайсці праз OAuth \ No newline at end of file From 49d752c685d069bfef919452e8396fa52aa21f06 Mon Sep 17 00:00:00 2001 From: Hinagiku Zeppeki Date: Mon, 12 Oct 2020 11:45:11 +0000 Subject: [PATCH 013/123] Translated using Weblate (Japanese) Currently translated at 98.5% (3454 of 3505 strings) --- OsmAnd/res/values-ja/strings.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index 35f9146fc7..16d59f00aa 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -1553,7 +1553,7 @@ POIの更新は利用できません 高さ制限 ルート上で通行可能な車両の高さを指定します。 スマートなルート再計算 - 経路が長い場合、最初の部分のみ再計算します。 + ルートの最初の部分のみを再計算します。旅程(設定ルート)がとても長い場合に効果的です。 明色 暗色 ピエモンテ語 @@ -3307,7 +3307,7 @@ POIの更新は利用できません アプリ全体に反映されます OsmAnd設定 他のプロファイルからコピー - 画面表示機能 + 画面の復帰設定 ナビゲーション中のマップ表示 ナビゲーション中のマップ表示 その他 @@ -3705,7 +3705,7 @@ POIの更新は利用できません グジャラート語 チュヴァシ語 OsmAnd GPXの形式が正しくありません。サポートチームに連絡しての調査をおすすめします。 - 画面のタイムアウト + 画面の消灯設定 画面復帰オプションを選択します(端末設定でロックされる場合は、OsmAndがバックグラウンド動作でないことを確認してください): 端末側の画面オフ(省電力)設定に従って画面を消灯します。 端末側の画面オフ設定を使用 @@ -3760,7 +3760,7 @@ POIの更新は利用できません 車両の重量を入力してください。一部のルートにおいては、大型車両では通行できない場合があります。 車の長さを入力してください。一部のルートにおいては、長い車両では通行できない場合があります。 常に - 画面制御 + 画面の制御 現在無効。 \'画面を表示する時間\'の下にある\'画面を常に表示\'への設定が必要です。 画面オフを維持 疑似メルカトル図法 @@ -3801,7 +3801,7 @@ POIの更新は利用できません アンインストール 一部の国では、スピードカメラの事前警告は法律で禁止されています。 \"%1$s\"がオンの場合、設定された動作時間はそちらに依存します。 - デフォルトの画面タイムアウト時間 + 端末設定の画面スリープ時間に従う トーン メートル 追加マップの詳細を表示または非表示にします @@ -3900,4 +3900,16 @@ POIの更新は利用できません 最終更新日 名称: 降順(Z-A) 名称: 昇順(A-Z) + Java(セーフモード)での公共交通機関ルーティング計算に切り替えます + OsmEdit機能を利用するには、OAuthでのログインが必要です。 + OAuthでログイン + OpenStreetMapOAuthトークンを消去する + ログアウトしました + 歩道を使わないようにします + 歩道を避ける + 開発 + OsmAnd Liveデータ + OsmAnd Liveデータ + 複雑なルート計算 + カーナビゲーション向けの2段階ルート計算です。 \ No newline at end of file From 91400aaa3fe361ea7118c96edecd976dc4d3ac48 Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sun, 11 Oct 2020 20:48:56 +0000 Subject: [PATCH 014/123] Translated using Weblate (Russian) Currently translated at 99.8% (3500 of 3505 strings) --- OsmAnd/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index d2c1dfd578..50a4f8b4f6 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -2269,7 +2269,7 @@ Удалить действие Вы уверены, что хотите удалить действие «%s»\? Показать избранные - Скрыть сохранённые + Скрыть избранные Показать/скрыть POI Показать %1$s Скрыть %1$s From 4e36a12c9e2499d112304d0980a4cc3ce0ad4119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 11 Oct 2020 18:54:59 +0000 Subject: [PATCH 015/123] Translated using Weblate (Turkish) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-tr/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 38dec6907b..21f675f31b 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -633,7 +633,7 @@ Yerel sürüm %1$d/%2$d öge devre dışı bırakıldı. %1$d/%2$d öge silindi. - %1$d/%2$d öge aktifleştirildi. + %1$d/%2$d öge etkinleştirildi. Harita dosyalarını yönetin. Aktifleştir Pasifleştir @@ -641,7 +641,7 @@ Adres Verisi Toplu taşıma verileri Harita Verisi - Pasifleştir + Devre dışı Sesli uyarılar (TTS) Sesli uyarılar (kaydedilmiş) POI Verisi @@ -2094,7 +2094,7 @@ Silindi Değiştirildi Eklendi - İşaretleyici %s aktifleştirildi. + %s işaretleyicisi etkinleştirildi. İçerik menüsünü açmadan, harita üzerinde bir belirtecin üzerine bas ve aktif belirteçlerin üzerine sürükle. \'Tek basış\' aktif Not alın! From 0be0f905a9c0166ffac0c21cfb3a3322aafe62be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20=C3=87akmak?= Date: Sun, 11 Oct 2020 13:49:12 +0000 Subject: [PATCH 016/123] Translated using Weblate (Turkish) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-tr/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 21f675f31b..07ec369a18 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -1459,7 +1459,7 @@ İzleme topu kullan Arka plan servisi tarafından kullanılan uyanma aralığı: Arka plan servisi tarafından kullanılan konum yöntemi: - Baş + Düz gidin Ekran yönlendirme Hiçbir adres belirlenmedi Sesli uyarılar @@ -3578,7 +3578,7 @@ Haritadaki Wikipedia makalelerinin dillerini seçin. Makaleyi okurken kullanılabilir herhangi bir dile geçiş yapın. Bazı Wikipedia makaleleri sizin dilinizde mevcut olmayabilir. Kantonca - Güney Min + Güney Mince Yorubaca Varayca Özbekçe @@ -3591,7 +3591,7 @@ Nepalce Napolice Birmanca - Minangkabau dili + Minangkabauca Malgaşça Kırgızca Kazakça From 307ab348f79838f64249706f1fdd94c3e345952a Mon Sep 17 00:00:00 2001 From: Athoss Date: Mon, 12 Oct 2020 08:54:07 +0000 Subject: [PATCH 017/123] Translated using Weblate (Hungarian) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-hu/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index be5f333f2a..1dd28463f8 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -3739,11 +3739,11 @@ Kiegészítő adatok átvétele Az importált profil kiegészítő adatokat is tartalmaz. Koppintson az \"Importál\" gombra kizálólag a profiladatok importálásához vagy válassza a kiegészítő adatokat. Összes adat importálva innen: %1$s, az alábbi gombokkal megnyithatja az alkalmazás megfelelő részét az adatok kezeléséhez. - A fizetés a Google Play fiókhoz lesz felszámítva a vásárlás megerősítésekor. + A fizetést a vásárlás visszaigazolásakor a Google Play számlájára terheljük. \n -\nAz előfizetés automatikusan megújul, kivéve ha a meghosszabbítás napja előtt lemondásra kerül. A fókjához az új időszak (hónap/három hónap/év) díja kizárólag a meghosszabítás napján lesz felszámolva. +\nAz előfizetés automatikusan megújul, kivéve, ha azt a megújítási dátum előtt lemondja. A megújítási időszakra (hónap / 3 hónap / év) vonatkozóan a számláját csak a megújítás napján terheljük meg. \n -\nAz előfizetéseit a Google Play beállításainál tudja kezelni és lemondani. +\nAz előfizetéseket a Google Play beállításai között kezelheti és törölheti. Törli az útvonal soron következő célpontját. Amennyiben ez a végző célpont, a navigáció megáll. Tudjon meg többet az érdekes pontokról a Wikipédiából. Ez az Ön offline zsebútikönyve – egyszerűen kapcsolja be a Wikipédia-bővítményt, és élvezze az Ön körüli objektumokról szóló cikkeket. Salakmotor From f0646f2ebce91faeecd2512985a7e8866ba3f735 Mon Sep 17 00:00:00 2001 From: Andrej Shadura Date: Mon, 12 Oct 2020 14:03:40 +0000 Subject: [PATCH 018/123] Translated using Weblate (Slovak) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-sk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index dc0af642be..971edf9dda 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3919,7 +3919,7 @@ Údaje OsmAnd Live Komplexný výpočet trasy Dvojfázový výpočet trasy pre navigáciu auta. - Natívny vývoj hromadnej dopravy + Natívna hromadná doprava (vo vývoji) Prepnúť na výpočet trasy hromadnej dopravy v Jave (bezpečné) Čo je nové Vykonať prihlásenie cez OAuth pre použitie funkcií upravovania OSM From 09734b684c37e852f79e155d9703bb41e362ef88 Mon Sep 17 00:00:00 2001 From: Zmicer Turok Date: Tue, 13 Oct 2020 04:10:02 +0000 Subject: [PATCH 019/123] Translated using Weblate (Belarusian) Currently translated at 99.7% (3497 of 3505 strings) --- OsmAnd/res/values-be/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-be/strings.xml b/OsmAnd/res/values-be/strings.xml index ce32262235..fa503e2464 100644 --- a/OsmAnd/res/values-be/strings.xml +++ b/OsmAnd/res/values-be/strings.xml @@ -3958,7 +3958,7 @@ Назва: Я — А Назва: А — Я Што новага - Дзякуй за набыццё плагіну «Контурныя лініі» + Дзякуй за набыццё ўбудовы «Контурныя лініі» Плата за падпіску спаганяецца за абраны перыяд. Скасаваць яе на AppGallery можна у любы момант. Пры пацвярджэнні пакупкі аплата будзе спаганяцца з рахунка, звязанага з вашым акаўнтам AppGallery. \n From 2cea371c1c40862221e2ac9dc372fb47f40a4ebe Mon Sep 17 00:00:00 2001 From: Ahmad Alfrhood Date: Sun, 11 Oct 2020 10:24:12 +0000 Subject: [PATCH 020/123] Translated using Weblate (Arabic) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-ar/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index c695a1c813..3e067c722c 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -3901,4 +3901,16 @@ \nيمكنك إدارة وإلغاء الاشتراكات الخاصة بك عن طريق الانتقال إلى إعدادات AppGallery. تجنب الممرات تجنب الممرات + ما الجديد + تطوير + بيانات أوسماند لايف + بيانات أوسماند لايف + التوجيه المركب + التوجيه على مرحلتين لملاحة السيارة. + تطوير النقل العام المحلي + قم بالتبديل إلى Java (الآمن) حساب توجيه النقل العام + قم بإجراء تسجيل دخول إلى OAuth لاستخدام ميزات osmedit + تسجيل الدخول عبر OAuth + مسح رمز OpenStreetMap OAuth + تسجيل الخروج بنجاح \ No newline at end of file From 1ec102d2301f034c8900e43f3a02f8ddcd5461fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1ns?= Date: Sun, 11 Oct 2020 23:12:55 +0000 Subject: [PATCH 021/123] Translated using Weblate (Galician) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-gl/strings.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-gl/strings.xml b/OsmAnd/res/values-gl/strings.xml index 8e7a90f00a..0a65bf9b5a 100644 --- a/OsmAnd/res/values-gl/strings.xml +++ b/OsmAnd/res/values-gl/strings.xml @@ -3675,7 +3675,7 @@ Lon %2$s %1$s / %2$s "O pagamento será cobrado na túa conta da Google Play na confirmación da compra. \n -\n A subscrición é renovada de xeito automático, a menos que sexa desbotada antes da data de renovación. A túa conta será cobrada polo período de renovación (més/trimestre/ano) soamente na data de renovación. +\n A subscrición é renovada de xeito automático, a menos que sexa desbotada antes da data de renovación. A túa conta será cobrada polo período de renovación (mes/trimestre/ano) soamente na data de renovación. \n \n Podes xestionar e desbotar as túas subscricións entrando nos axustes da Google Play." Procurar tipos de PDI @@ -3945,4 +3945,9 @@ Lon %2$s Enrutamento de dúas fases para a navegación de automóbil Desenvolvemento do transporte público nativo Activar cálculo de enrutamento de transporte público do Java (seguro) + Novidades + Inicia sesión co OAuth para empregar as funcións de edición do OSM + Entrar polo OAuth + Limpar token do OpenStreetMap OAuth + Sesión rematada \ No newline at end of file From 740649651abad238b349b287567079abe2ebfc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1ns?= Date: Mon, 12 Oct 2020 16:54:53 +0000 Subject: [PATCH 022/123] Translated using Weblate (Galician) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-gl/phrases.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-gl/phrases.xml b/OsmAnd/res/values-gl/phrases.xml index a840ae5d86..b974255f35 100644 --- a/OsmAnd/res/values-gl/phrases.xml +++ b/OsmAnd/res/values-gl/phrases.xml @@ -65,7 +65,7 @@ Lomba pequena de velocidade Rodas Inspección técnica de vehículos - Lavadura de automóbiles + Lavado de automóbiles Gasolineira;Estación de combustíbel;Estación de servizo Ar comprimido Aparcadoiro From b7d223bcf66664dad8969a3dbc483dd8ba6a6ed2 Mon Sep 17 00:00:00 2001 From: Andrej Shadura Date: Mon, 12 Oct 2020 13:47:41 +0000 Subject: [PATCH 023/123] Translated using Weblate (Slovak) Currently translated at 95.2% (3645 of 3825 strings) --- OsmAnd/res/values-sk/phrases.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/OsmAnd/res/values-sk/phrases.xml b/OsmAnd/res/values-sk/phrases.xml index db5f8d31a6..7dc2cc7a4c 100644 --- a/OsmAnd/res/values-sk/phrases.xml +++ b/OsmAnd/res/values-sk/phrases.xml @@ -3653,4 +3653,15 @@ Nie Áno Kancelária taxislužby + Postbank + DecoTurf + Podológia + Zdravotná špecializácia: pôrodníctvo (cisársky rez): nie + Zdravotná špecializácia: sociálna pediatria: nie + Sociálna pediatria + Zdravotná špecializácia: pôrodníctvo (prenatálne): nie + Pôrodníctvo (prenatálne) + Zdravotná špecializácia: pôrodníctvo (postnatálne): nie + Rašelinisko + Slatina \ No newline at end of file From 5c3835bb66b91a4cf1b4436a0ee45a0cd6179482 Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Sun, 11 Oct 2020 17:53:34 +0000 Subject: [PATCH 024/123] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index 8f1641cca9..514414ef4c 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -2821,7 +2821,7 @@ Renova anualmente Renova trimestralmente Por OsmAnd - %1$,2f %2$s + %1$.2f %2$s Período de pagamento: As doações ajudam a financiar a cartografia no OSM. • Navegação: Corrige barra de progresso, inversão rápida de início e fim de rota From 32a24158bd776e40f72e147a8391d23ebb254491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 11 Oct 2020 11:25:42 +0000 Subject: [PATCH 025/123] Translated using Weblate (Estonian) Currently translated at 99.2% (3477 of 3505 strings) --- OsmAnd/res/values-et/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-et/strings.xml b/OsmAnd/res/values-et/strings.xml index c580533de8..fdd760f307 100644 --- a/OsmAnd/res/values-et/strings.xml +++ b/OsmAnd/res/values-et/strings.xml @@ -3593,7 +3593,7 @@ Kuna mõnedel marsruutidel võidakse kohaldada sõidukite kõrguspiiranguid, siis palun märkige oma sõiduki kõrgus. Kuna mõnedel marsruutidel võidakse kohaldada piiranguid pikkade sõidukite suhtes, siis palun märkige oma sõiduki pikkus. Kuna mõnedel marsruutidel võidakse kohaldada sõidukite kaalupiiranguid, siis palun märkige oma sõiduki kaal. - OsmAnd GPX faili vorming pole korrektne. Täpsemaks uurimiseks palun suhtle meie tugimeeskonnaga. + OsmAnd GPX-andmestiku vorming on vigane. Täpsemaks uurimiseks palun võta ühendust meie kasutajatoega. Sorteeri kategooria järgi Jätka Imporditavas profiilis leidub täiendavaid andmeid. Vajutades „Impordi“ imporditakse vaid profiili andmed, täiendavate andmete jaoks pead märkima vastava valiku. From 463ce61b8455cc512dccabc4bb85c83e6a99c16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 11 Oct 2020 20:12:23 +0000 Subject: [PATCH 026/123] Translated using Weblate (Turkish) Currently translated at 78.1% (2988 of 3825 strings) --- OsmAnd/res/values-tr/phrases.xml | 88 +++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index 16005d8a23..99b07f5d00 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -366,7 +366,7 @@ Depolama Çöp bertaraf Çöp tenekesi - Sanayi Bölgesi + Sanayi bölgesi Taş ocağı Üzüm bağı Meyve bahçesi @@ -2801,7 +2801,7 @@ Danışma (uyuşturucu): evet Danışma (bağımlılık): evet Danışma (bağımlılık): evet - Sağlık çalışanının işi: psikolog + Sağlık çalışanının rolü: psikolog İlk yardım çantası Duvar Yeraltı @@ -2915,4 +2915,88 @@ Şarküteri Turta Çay dükkanı + Sağlık çalışanının rolü: büyücü + Sağlık çalışanının rolü: teknisyen + Sağlık çalışanının rolü: doktor asistanı + Sağlık çalışanının rolü: terapist + Sağlık çalışanının rolü: podolog + Sağlık çalışanının rolü: doktor + Sağlık çalışanının rolü: sağlık görevlisi + Sağlık çalışanının rolü: hemşire + Sağlık çalışanının rolü: ebe + Sağlık çalışanının rolü: şifacı + Sağlık çalışanının rolü: asistan + Emme noktası + Sağlık tesisi türü: destek grubu evi + Sağlık tesisi türü: huzurevi + Sağlık merkezi türü: bölüm + Sağlık hizmeti: test: hayır + Sağlık hizmeti: test: evet + Sağlık hizmeti: destek: hayır + Sağlık hizmeti: destek: evet + Sağlık hizmeti: önleme: hayır + Sağlık hizmeti: önleme: evet + Sağlık hizmeti: çocuk bakımı: hayır + Sağlık hizmeti: çocuk bakımı: evet + Sağlık hizmeti: muayene: hayır + Sağlık hizmeti: muayene: evet + Sağlık merkezi türü: ilk yardım + Sağlık merkezi türü: dispanser + Sağlık merkezi türü: terapi + Sağlık merkezi türü: danışma merkezi + Tıbbi ofis + Sert serbest uçuş: hayır + Sert + Yelken kanatla uçuş: hayır + Yelken kanatla uçuş + Yamaç paraşütü: hayır + Yamaç paraşütü + Resmi: hayır + Resmi: evet + Eğitim alanı + Çekme alanı + Zirve iniş alanı + İniş alanı + Kalkış alanı + Düğme ile etkinleştirilir: hayır + Düğme ile etkinleştirilir: evet + Serbest uçuş (spor) + Patlama: aygıt + Patlama: savaş başlığı + Patlama: krater çapı + Patlama salvosu: bir salvo testinin ikinci veya daha sonraki patlaması + Patlama salvosu: bir salvo testinin ilk patlaması + Vücut dalgası büyüklüğü + Patlama merkezinin yüksekliği + Patlama boyu + Patlama deliği + Patlama gücü + Patlama amacı: sanayi uygulaması, toprak kaydırma + Patlama amacı: sanayi uygulaması + Patlama amacı: sanayi uygulaması, petrol uyarımı + Patlama amacı: sanayi uygulaması, sismik sondaj + Patlama amacı: sanayi uygulaması, kazı çalışması + Patlama amacı: temel bilim + Patlama amacı: barışçıl uygulamalar için araştırma + Patlama amacı: güvenlik deneyi + Patlama amacı: silah etkileri + Patlama amacı: nükleer silahlarla ilgili + Patlama serisi + Kod adı (ingilizce) + Patlama türü: su altı + Patlama türü: uzay (80 km\'nin üzerindeki yükseklik) + Patlama türü: atmosferik, roket veya füze + Patlama türü: krater patlaması (sığ yüzey altı) + Patlama türü: atmosferik, su yüzeyi, mavna + Patlama türü: atmosferik, yüzey + Patlama türü: atmosferik, balon + Patlama türü: atmosferik, yüzey, kule + Patlama türü: atmosferik, havadan atma + Patlama türü: atmosferik + Patlama türü: yeraltı, kuyu + Patlama: alan + Koruma türü + Bandy + Sokak dolabı + Ateş çukuru \ No newline at end of file From b8d15c06274fa0c4172b533bcf8cdbb9d772d8f6 Mon Sep 17 00:00:00 2001 From: Verdulo Date: Mon, 12 Oct 2020 19:06:28 +0000 Subject: [PATCH 027/123] Translated using Weblate (Esperanto) Currently translated at 100.0% (3505 of 3505 strings) --- OsmAnd/res/values-eo/strings.xml | 34 ++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index 50bc2c4390..727be72322 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -1853,7 +1853,7 @@ Erara formo: %s Vi devas esti konektita al la interreto por instali tiun ĉi kromprogramon. Inteligenta rekalkulado de kurso - Por longaj vojaĝoj, rekalkuli nur komencan parton de kurso. + Rekalkuli nur komencan parton de kurso. Uzebla por longaj kursoj. Taksi tiun ĉi aplikaĵon Via opinio estas grava por ni. Bonvolu taksi OsmAnd ĉe Google Play @@ -1890,7 +1890,7 @@ Ĉiuj nekonservitaj ŝanĝoj estos forigitaj. Ĉu pluigi? ankoraŭ %1$s elŝutoj Vojoj - Elŝutado - %1$s dosiero + Elŝutado – %1$d dosiero Montri reklamaĵon de senpaga versio Montri reklamaĵon de senpaga versio, eĉ se vi havas pagan version. Bonvolu aktivigi la kromprogramon “mara map-vido” @@ -2832,13 +2832,13 @@ Ĉiu-3-monate Ĉiujare %1$s / monato - %1$,2f %2$s / monato + %1$.2f %2$s / monato Ŝparu %1$s Nuna kotizo Reaboni ĉiumonate Reaboni ĉiukvaronjare Reaboni ĉiujare - %1$,2f %2$s + %1$.2f %2$s Intertempo de pagoj: Donacoj helpos fondi kartografion de OSM. De OsmAnd @@ -3651,7 +3651,7 @@ %1$s / %2$s La pago estos prenita el via konto Google Play post konfirmi aĉeton. \n -\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la data de renoviĝo. +\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la dato de renoviĝo. \n \n Vi povas administri kaj rezigni viajn abonojn per agordoj de Google Play. Miksi specojn de interesejoj el diversaj kategorioj. Frapetu ŝaltilon por elekti la tutan kategorion, frapetu ĉe maldekstre por elekti detala(j)n objekto(j)n el la kategorio. @@ -3894,7 +3894,7 @@ Simpligita spuro Nur la linio de kurso estos konservita, la navigadpunktoj estos forigitaj. Dosiernomo - %d dosieroj de spuroj elektitaj + %s dosieroj de spuroj elektitaj Paŭzigos registri spuron je halto de la aplikaĵo (per la menuo de lastaj aplikaĵoj). (Fona emblemo de OsmAnd malaperos de la androida sciiga zono.) Paŭzigi registri spuron Daŭrigi registri spuron @@ -3903,4 +3903,26 @@ Antaŭa segmento Ĉiuj antaŭaj segmentoj Nur la elektita segmento estos rekalkulita uzante la elektitan profilon. + Kio estas nova + La pago estos prenita el via konto AppGallery post konfirmi aĉeton. +\n +\n La abono aŭtomate renoviĝos escepte se ĝi estos nuligita antaŭ la dato de renoviĝo. La pago estos prenita por la renoviĝa periodo (monato/tri monatoj/jaro) nur je la dato de renoviĝo. +\n +\n Vi povas administri kaj rezigni viajn abonojn per la agordoj de AppGallery. + Emblemoj komenco/fino + Dankon al vi por aĉeti la kromprogramon “nivelkurboj” + Abonpago prenita por la elektita periodo. VI ĉiam povas rezigni abonon ĉe AppGallery. + Eviti irejojn + Sen trotuaroj + Programado + Datumoj OsmAnd Live + Datumoj OsmAnd Live + Komplika kurs-difinado + Du-faza difinado de kurso por aŭtomobila navigo. + Indiĝena metodo de publik-transporta navigo + Aktivigi (sekuran) metodon por kalkuli kursojn de publika transporto uzante programlingvon Java + Ensaluti uzante OAuth por redakti la mapon OSM + Ensaluti per OAuth + Forigi ĵetonon OpenStreetMap OAuth + Sukcese elsalutinta \ No newline at end of file From 928ed7b2a46010946b2933ac5eee3841c78a98df Mon Sep 17 00:00:00 2001 From: Verdulo Date: Mon, 12 Oct 2020 19:08:23 +0000 Subject: [PATCH 028/123] Translated using Weblate (Esperanto) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-eo/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-eo/phrases.xml b/OsmAnd/res/values-eo/phrases.xml index 8dc446b363..df01830535 100644 --- a/OsmAnd/res/values-eo/phrases.xml +++ b/OsmAnd/res/values-eo/phrases.xml @@ -3837,4 +3837,5 @@ prokrasto Tabulo de forveturoj Plenigi per trinkebla akvo + tergaso likva (LNG) \ No newline at end of file From 9e2709cc3689cc3d8c64d9160955082fe2d45b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Mon, 12 Oct 2020 18:28:39 +0000 Subject: [PATCH 029/123] Translated using Weblate (Icelandic) Currently translated at 99.8% (3501 of 3505 strings) --- OsmAnd/res/values-is/strings.xml | 79 +++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/OsmAnd/res/values-is/strings.xml b/OsmAnd/res/values-is/strings.xml index d2404aaba2..c5ce396f03 100644 --- a/OsmAnd/res/values-is/strings.xml +++ b/OsmAnd/res/values-is/strings.xml @@ -530,7 +530,7 @@ Víðværar stillingar Almennt Bakgrunnshamur - Eyða \'%s\'? + Eyða %1$s\? Úthverfi Byggðakjarni Þorp @@ -709,8 +709,8 @@ Leyfa hraðbrautir. Wikipedia-greinar í nágrenninu Útreikningur leiðar - Þú ert ekki með neina GPX-ferla ennþá - Þú getur líka bætt GPX-skrám í möppuna + Þú ert ekki ennþá með neina ferla + Þú getur líka bætt ferilskrám í möppuna Virkja hraðskráningu ferðar Ferð Skráð @@ -723,7 +723,7 @@ Engin internettenging Nauðsynleg til að sækja kort. Leita að stað… - Gagnageymsla OsmAnd (fyrir kort, GPX-ferla, o.s.frv.): %1$s. + Gagnageymsla OsmAnd (fyrir kort, ferilskrár, o.s.frv.): %1$s. Gefa heimild Ekki sýna nýjar útgáfur Komast í gang @@ -1718,7 +1718,7 @@ Stærð leturs fyrir nöfn á kortinu: Aflúsunarupplýsingar myndgerðar Birta afköst myndgerðar. - Snúa við stefnu GPX + Snúa við stefnu ferils Frálag raddleiðsagnar Hljóð í margmiðlun/tónlist Nota rastakort fyrir allt undir þessu aðdráttarstigi. @@ -1746,7 +1746,7 @@ Stigvaxandi leit byggingar Upplýsingum um hnút var ekki hlaðið inn Snjöll endurreiknun leiðar - Einungis endurreikna upphafshluta leiðar fyrir langar ferðir. + Endurreiknar aðeins upphafshluta leiðar. Má nota fyrir langar ferðir. Kortið sem eingöngu er með vegum er ekki nauðsynlegt, þar sem þú ert þegar með staðlaða (fulla útgáfu) kortsins. Sækja það samt? Birta dýptarlínur og punkta. Dýptarlínur sjávar @@ -2116,7 +2116,7 @@ Gat ekki flutt skrána inn. Vinsamlegast athugið hvort OsmAnd hafi réttindi til að lesa skrána þar sem hún er. Opna Mapillary Mapillary græja - Götumyndir + Mapillary götumyndir Mapillary-mynd Mælistika út frá miðju hrings Heimildir @@ -2904,10 +2904,10 @@ Gerðir vega Fara útaf við Fara um borð við stöðvunina - Birta/Fela GPX-ferla - Hnappur til að birta eða fela valda GPX-ferla á kortinu. - Fela GPX-ferla - Birta GPX-ferla + Birta/Fela ferla + Hnappur til að birta eða fela valda ferla á kortinu. + Fela ferla + Birta ferla • Nýr skjár fyrir \'Leiðir\': Birtir leiðarhnappa fyrir \'Heim\' og \'Vinna\', flýtileið á fyrri leið, listi yfir virka GPX-ferla og merki, leitarferill \n \n • Viðbótarupplýsingar undir \'Nánar um leið\': gerð vegar, yfirborð, bratti, áferð @@ -3766,7 +3766,7 @@ Víxlhnappur til að birta eða fela Mapillary-lagið á kortinu. Stefna %1$s eytt - Endurræsing er nauðsynleg til að geta fjarlægt alveg gögn um hraðamyndavélar. + Endurræstu forritið til að fjarlægja öll gögn um hraðamyndavélar. Fjarlægja og endurræsa Fjarlægja Aðvaranir vegna hraðamyndavéla eru bannaðar með lögum í sumum löndum. @@ -3777,19 +3777,19 @@ \n \nVeldu %2$s: öllum gögnum sem tengjast hraðamyndavélum; t.d. aðvaranir, tilkynningar, staðsetningar o.fl. verður eytt þar til OsmAnd er sett inn aftur frá grunni. Veldu ferilskrá þar sem nýjum bút verður bætt inn. - Veldu hvernig eigi að tengja punkta; með beinni línu eða reikna leið milli þeirra með þessu sniði. + Veldu hvernig eigi að tengja punkta; með beinni línu eða reikna leið milli þeirra eins og tiltekið er hér að neðan. Allur ferillinn verður endurreiknaður með völdu sniði. Lokaður OSM-minnispunktur Allur ferillinn - Sýna tákn fyrir upphaf/enda + Sýna tákn fyrir upphaf og enda Þetta tæki er ekki með hraðamyndavélar. Gókart - Þú verður að skilgreina virka daga til að halda áfram + Skilgreindu virka daga til að halda áfram Eyða næsta markpunkti Leiðsagnarsnið Hjólastóll áfram Náðu í upplýsingar um merka staði frá Wikipedia. Þetta er þá orðið að vasaleiðsögn án nettengingar - bara virkjaðu Wikipedia-viðbótina og njóttu þess að geta lesið um hlutina í kringum þig. - Virkja þetta til að hægt sé að stýra aðdráttarstigi korts með hljóðstyrkshnöppum. + Stýrðu aðdráttarstigi korts með hljóðstyrkshnöppum tækisins. Tilgreindu lengd farartækis sem leyfð er á leiðum. Ef stefna er öfug Aðeins næsti bútur verður endurreiknaður með völdu sniði. @@ -3840,7 +3840,7 @@ \n Veldu bilið þar sem merki með tíma eða vegalengd á ferlinum verða birt. Tengja við vegina - Ertu viss um að þú viljir loka leiðaskipulagningu án þess að vista\? Þú munt tapa öllum breytingum. + Ertu viss um að þú viljir loka leiðaskipulagningu án þess að vista\? Vista sem ferilskrá Þolvik vegalengdar Skrifa feril í GPX-skrá @@ -3856,11 +3856,9 @@ Upphaf ferils Ferlar Settu inn heimilisfang - Veldu ferilskrá til að fylgja eða flyttu inn úr tæki. + Veldu ferilskrá til að fylgja eða flyttu hana inn úr tækinu þínu. Bæta í ferilskrá - Til að nota þennan valkost þarf OsmAnd að festa ferilinn þinn við vegi á kortinu. -\n -\nÍ næsta skrefi þarftu að velja leiðsagnarsnið svo hægt sé að finna hvaða vegir séu leyfilegir og hvaða þolvik vegalengdar eigi að miða við til að nálga ferilinn þinn við fyrirliggjandi vegi. + Í næsta skrefi þarftu að velja leiðsagnarsnið svo hægt sé að finna hvaða vegir séu leyfilegir og hvaða þolvik vegalengdar eigi að miða við til að nálga ferilinn þinn við fyrirliggjandi vegi. Bæta við ferilskrám Línuskautar Bæta við heimilisfangi @@ -3868,7 +3866,7 @@ Skráning ferðar Næsti punktur Ferlar - GPX + REC Klippa á undan Bæta við leiðarpunkti í feril Skipta um gerð leiðar eftir @@ -3879,4 +3877,41 @@ Skrá leið í feril Velja annan feril Skrifa feril sjálfkrafa í GPX-skrá á meðan leiðsögn stendur + Nýjungar + Einfaldaður ferill + Aðeins leiðarlínan verður vistuð, ferilpunktunum verður eytt. + Skráarheiti + Sjálfgefið í kerfinu + Allir bútar í kjölfarið + Fyrri bútur + Allir fyrri bútar + Aðeins valinn bútur verður endurreiknaður með völdu sniði. + Allir bútar sem á eftir fylgja verða endurreiknaðir með völdu sniði. + Allir fyrri bútar verða endurreiknaðir með völdu sniði. + Opna vistaðan feril + er vistað + Bættu við a.m.k. tveimur punktum. + Endurtaka + Síðast breytt + Nafn: Ö – A + Nafn: A – Ö + Tákn við upphaf/enda + Forðast gangstéttir + Forðast gangstéttir + Þróun + OsmAnd Live gögn + OsmAnd Live gögn + Flókin leiðagerð + Tveggja-þátta leiðagerð fyrir bílaleiðsögn. + Innbyggð þróun almenningssamgangna + Skipta yfir í Java (öruggt) útreikning fyrir almenningssamgöngur + Framkvæma OAuth-innskráningu til að nota osmedit-eiginleika + Skrá inn í gegnum OAuth + Hreinsa OAuth-teikn OpenStreetMap + Útskráning tókst + %s GPX-skrár valdar + Mun setja GPX-skráningu í bið þegar forritið er drepið (slökkt á því í gegnum skjáinn fyrir nýleg forrit - bakgrunnsvísir OsmAnd hverfur þar með úr tilkynningastiku Android-kerfisins.) + Veldu millibil skráninga í almenna leiðarskráningu (virkjað með viðmótshlutanum fyrir GPX-skráningu á kortinu). + Setja skráningu í bið + Halda áfram með skráningu \ No newline at end of file From 5dd9655c086aeb2b8164845afefd0e386cb12bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Mon, 12 Oct 2020 16:19:35 +0000 Subject: [PATCH 030/123] Translated using Weblate (Icelandic) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-is/phrases.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OsmAnd/res/values-is/phrases.xml b/OsmAnd/res/values-is/phrases.xml index 2c6a85bdb4..07ff354d7e 100644 --- a/OsmAnd/res/values-is/phrases.xml +++ b/OsmAnd/res/values-is/phrases.xml @@ -3827,4 +3827,7 @@ Lyfta Smærri raftæki Áfylling drykkjarvatns + LNG + Brottfaratafla: nei + Brottfaratafla \ No newline at end of file From 38228884ef134097777af457183142f834fc9319 Mon Sep 17 00:00:00 2001 From: Andrej Shadura Date: Mon, 12 Oct 2020 13:54:49 +0000 Subject: [PATCH 031/123] Translated using Weblate (Belarusian) Currently translated at 100.0% (271 of 271 strings) Translation: OsmAnd/Telegram Translate-URL: https://hosted.weblate.org/projects/osmand/telegram/be/ --- OsmAnd-telegram/res/values-be/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) 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 From ff06ccb000709cfe46c122654ba759dd406dfd6d Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 13 Oct 2020 09:45:24 +0300 Subject: [PATCH 032/123] Fix merge --- OsmAnd/src/net/osmand/aidl/ConnectedApp.java | 1 - 1 file changed, 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/aidl/ConnectedApp.java b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java index a637bda2d2..bbfa4f89b8 100644 --- a/OsmAnd/src/net/osmand/aidl/ConnectedApp.java +++ b/OsmAnd/src/net/osmand/aidl/ConnectedApp.java @@ -18,7 +18,6 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.layers.AidlMapLayer; import net.osmand.plus.views.layers.MapInfoLayer; From 52d3fae7c76eaa6811d7068a4a9a157956a56bd2 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Tue, 13 Oct 2020 11:03:55 +0300 Subject: [PATCH 033/123] Add blog article from help --- OsmAnd/build.gradle | 3 +++ OsmAnd/src/net/osmand/plus/activities/HelpActivity.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index ee9d4dd684..c2361dd165 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -331,6 +331,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" } diff --git a/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java b/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java index 2d4a0c96c5..b7b3044f97 100644 --- a/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/HelpActivity.java @@ -173,7 +173,7 @@ public class HelpActivity extends OsmandActionBarActivity implements AdapterView contextMenuAdapter.addItem(createItem(R.string.versions_item, NULL_ID, "feature_articles/changes.html")); contextMenuAdapter.addItem(createItem(R.string.what_is_new, NULL_ID, - "feature_articles/blog.html")); + "feature_articles/osmand-3-8-released.html")); String releasedate = ""; if (!this.getString(R.string.app_edition).equals("")) { From 3ef12afb08841818d4151bb53c5bcc706295e628 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Tue, 13 Oct 2020 12:27:24 +0300 Subject: [PATCH 034/123] show progress bar while route is recalculating --- .../fragment_measurement_tool_graph.xml | 10 ++ OsmAnd/res/values/strings.xml | 1 + .../MeasurementToolFragment.java | 6 +- .../plus/measurementtool/MtGraphFragment.java | 102 +++++++++++------- 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/OsmAnd/res/layout/fragment_measurement_tool_graph.xml b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml index dd9d3b7cae..3d14187712 100644 --- a/OsmAnd/res/layout/fragment_measurement_tool_graph.xml +++ b/OsmAnd/res/layout/fragment_measurement_tool_graph.xml @@ -85,6 +85,16 @@ android:tint="?attr/default_icon_color" tools:src="@drawable/ic_action_info_dark" /> + + + Wait for the route recalculation.\nGraph will be available after recalculation. %1$s data available only on the roads, you need to calculate a route using “Route between points” to get it. Graph Logout successful diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index e5737ceca1..671539ffb8 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -24,7 +24,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.core.widget.TextViewCompat; -import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -222,6 +221,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route @Override public void showProgressBar() { MeasurementToolFragment.this.showProgressBar(); + updateAdditionalInfoView(); } @Override @@ -659,6 +659,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route progressBarVisible = true; } + public boolean isProgressBarVisible() { + return progressBarVisible; + } + private void updateMainIcon() { GpxData gpxData = editingCtx.getGpxData(); if (gpxData != null) { diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index 71a7c45db2..c8f6e3cf2d 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -2,6 +2,7 @@ package net.osmand.plus.measurementtool; import android.view.View; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; @@ -55,11 +56,6 @@ public class MtGraphFragment extends BaseCard private List graphTypes = new ArrayList<>(); private MeasurementToolFragment mtf; - public MtGraphFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { - super(mapActivity); - this.mtf = mtf; - } - private enum CommonGraphType { OVERVIEW(R.string.shared_string_overview, false), ALTITUDE(R.string.altitude, true), @@ -75,6 +71,37 @@ public class MtGraphFragment extends BaseCard final boolean canBeCalculated; } + public MtGraphFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { + super(mapActivity); + this.mtf = mtf; + } + + @Override + protected void updateContent() { + if (mapActivity == null || mtf == null) return; + editingCtx = mtf.getEditingCtx(); + + commonGraphContainer = view.findViewById(R.id.common_graphs_container); + customGraphContainer = view.findViewById(R.id.custom_graphs_container); + messageContainer = view.findViewById(R.id.message_container); + commonGraphChart = (LineChart) view.findViewById(R.id.line_chart); + customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); + updateGraphData(); + + rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); + rvGraphTypesMenu.setLayoutManager( + new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); + + refreshGraphTypesSelectionMenu(); + GraphType firstAvailableGraphType = getFirstAvailableGraphType(); + setupVisibleGraphType(firstAvailableGraphType); + } + + @Override + public int getCardLayoutId() { + return R.layout.fragment_measurement_tool_graph; + } + private void refreshGraphTypesSelectionMenu() { rvGraphTypesMenu.removeAllViews(); OsmandApplication app = getMyApplication(); @@ -112,18 +139,18 @@ public class MtGraphFragment extends BaseCard public void onUpdateAdditionalInfo() { updateGraphData(); refreshGraphTypesSelectionMenu(); - setupVisibleGraphType(currentGraphType); + setupVisibleGraphType(currentGraphType.isAvailable() + ? currentGraphType : getFirstAvailableGraphType()); } - private void setupVisibleGraphType(GraphType preferredType) { - currentGraphType = currentGraphType != null && preferredType.hasData() - ? preferredType : getFirstAvailableGraphType(); + private void setupVisibleGraphType(GraphType type) { + currentGraphType = type; updateDataView(); } private GraphType getFirstAvailableGraphType() { for (GraphType type : graphTypes) { - if (type.hasData() || type.canBeCalculated()) { + if (type.isAvailable()) { return type; } } @@ -131,13 +158,27 @@ public class MtGraphFragment extends BaseCard } private void updateDataView() { - if (currentGraphType.hasData()) { + if (mtf.isProgressBarVisible()) { + showProgressMessage(); + } else if (currentGraphType.hasData()) { showGraph(); } else if (currentGraphType.canBeCalculated()) { showMessage(); } } + private void showProgressMessage() { + commonGraphContainer.setVisibility(View.GONE); + customGraphContainer.setVisibility(View.GONE); + messageContainer.setVisibility(View.VISIBLE); + TextView tvMessage = messageContainer.findViewById(R.id.message_text); + ImageView icon = messageContainer.findViewById(R.id.message_icon); + ProgressBar pb = messageContainer.findViewById(R.id.progress_bar); + pb.setVisibility(View.VISIBLE); + icon.setVisibility(View.GONE); + tvMessage.setText(R.string.message_graph_will_be_available_after_recalculation); + } + private void showGraph() { if (currentGraphType.isCustom()) { customGraphChart.clear(); @@ -160,8 +201,12 @@ public class MtGraphFragment extends BaseCard messageContainer.setVisibility(View.VISIBLE); TextView tvMessage = messageContainer.findViewById(R.id.message_text); ImageView icon = messageContainer.findViewById(R.id.message_icon); - String message = app.getString(R.string.message_need_calculate_route_before_show_graph, currentGraphType.getTitle()); - tvMessage.setText(message); + ProgressBar pb = messageContainer.findViewById(R.id.progress_bar); + pb.setVisibility(View.GONE); + icon.setVisibility(View.VISIBLE); + tvMessage.setText(app.getString( + R.string.message_need_calculate_route_before_show_graph, + currentGraphType.getTitle())); icon.setImageResource(R.drawable.ic_action_altitude_average); } @@ -296,33 +341,6 @@ public class MtGraphFragment extends BaseCard defaultRender, currentSearchRequest, defaultSearchRequest); } - @Override - public int getCardLayoutId() { - return R.layout.fragment_measurement_tool_graph; - } - - @Override - protected void updateContent() { - if (mapActivity == null || mtf == null) return; - - editingCtx = mtf.getEditingCtx(); - OsmandApplication app = mapActivity.getMyApplication(); - - commonGraphContainer = view.findViewById(R.id.common_graphs_container); - customGraphContainer = view.findViewById(R.id.custom_graphs_container); - messageContainer = view.findViewById(R.id.message_container); - commonGraphChart = (LineChart) view.findViewById(R.id.line_chart); - customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); - updateGraphData(); - - rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); - rvGraphTypesMenu.setLayoutManager( - new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); - - refreshGraphTypesSelectionMenu(); - setupVisibleGraphType(currentGraphType); - } - private static class GraphType { private String title; private boolean isCustom; @@ -344,6 +362,10 @@ public class MtGraphFragment extends BaseCard return isCustom; } + public boolean isAvailable() { + return hasData() || canBeCalculated(); + } + public boolean canBeCalculated() { return canBeCalculated; } From f158381f5f38e6d0fd134a8a7122e1fc3853c685 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Tue, 13 Oct 2020 13:06:27 +0300 Subject: [PATCH 035/123] fix "visible graph doesn't update after data updated" --- .../plus/measurementtool/MtGraphFragment.java | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index c8f6e3cf2d..f3b4bbcf8c 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -52,7 +52,7 @@ public class MtGraphFragment extends BaseCard private RecyclerView rvGraphTypesMenu; private MeasurementEditingContext editingCtx; - private GraphType currentGraphType; + private GraphType visibleGraphType; private List graphTypes = new ArrayList<>(); private MeasurementToolFragment mtf; @@ -93,8 +93,7 @@ public class MtGraphFragment extends BaseCard new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); refreshGraphTypesSelectionMenu(); - GraphType firstAvailableGraphType = getFirstAvailableGraphType(); - setupVisibleGraphType(firstAvailableGraphType); + updateDataView(); } @Override @@ -118,7 +117,7 @@ public class MtGraphFragment extends BaseCard } } adapter.setItems(items); - String selectedItemKey = currentGraphType != null ? currentGraphType.getTitle() : items.get(0).getTitle(); + String selectedItemKey = visibleGraphType.getTitle(); adapter.setSelectedItemByTitle(selectedItemKey); adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() { @Override @@ -126,7 +125,7 @@ public class MtGraphFragment extends BaseCard adapter.setItems(items); adapter.setSelectedItem(item); GraphType chosenGraphType = (GraphType) item.getObject(); - if (chosenGraphType != null && !chosenGraphType.equals(currentGraphType)) { + if (!isCurrentVisibleType(chosenGraphType)) { setupVisibleGraphType(chosenGraphType); } } @@ -139,15 +138,21 @@ public class MtGraphFragment extends BaseCard public void onUpdateAdditionalInfo() { updateGraphData(); refreshGraphTypesSelectionMenu(); - setupVisibleGraphType(currentGraphType.isAvailable() - ? currentGraphType : getFirstAvailableGraphType()); + updateDataView(); } private void setupVisibleGraphType(GraphType type) { - currentGraphType = type; + visibleGraphType = type; updateDataView(); } + private boolean isCurrentVisibleType(GraphType type) { + if (visibleGraphType != null && type != null) { + return Algorithms.objectEquals(visibleGraphType.getTitle(), type.getTitle()); + } + return false; + } + private GraphType getFirstAvailableGraphType() { for (GraphType type : graphTypes) { if (type.isAvailable()) { @@ -160,9 +165,9 @@ public class MtGraphFragment extends BaseCard private void updateDataView() { if (mtf.isProgressBarVisible()) { showProgressMessage(); - } else if (currentGraphType.hasData()) { + } else if (visibleGraphType.hasData()) { showGraph(); - } else if (currentGraphType.canBeCalculated()) { + } else if (visibleGraphType.canBeCalculated()) { showMessage(); } } @@ -180,18 +185,18 @@ public class MtGraphFragment extends BaseCard } private void showGraph() { - if (currentGraphType.isCustom()) { + if (visibleGraphType.isCustom()) { customGraphChart.clear(); commonGraphContainer.setVisibility(View.GONE); customGraphContainer.setVisibility(View.VISIBLE); messageContainer.setVisibility(View.GONE); - prepareCustomGraphView((BarData) currentGraphType.getData()); + prepareCustomGraphView((BarData) visibleGraphType.getData()); } else { commonGraphChart.clear(); commonGraphContainer.setVisibility(View.VISIBLE); customGraphContainer.setVisibility(View.GONE); messageContainer.setVisibility(View.GONE); - prepareCommonGraphView((LineData) currentGraphType.getData()); + prepareCommonGraphView((LineData) visibleGraphType.getData()); } } @@ -206,7 +211,7 @@ public class MtGraphFragment extends BaseCard icon.setVisibility(View.VISIBLE); tvMessage.setText(app.getString( R.string.message_need_calculate_route_before_show_graph, - currentGraphType.getTitle())); + visibleGraphType.getTitle())); icon.setImageResource(R.drawable.ic_action_altitude_average); } @@ -255,6 +260,18 @@ public class MtGraphFragment extends BaseCard graphTypes.add(new GraphType(title, true, false, data)); } } + + // update current visible graph type + if (visibleGraphType == null) { + visibleGraphType = getFirstAvailableGraphType(); + } else { + for (GraphType type : graphTypes) { + if (isCurrentVisibleType(type)) { + visibleGraphType = type.isAvailable() ? type : getFirstAvailableGraphType(); + break; + } + } + } } private List getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) { From c75aa594eacc9ffd924ab94ebbbdffce0c4044ef Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Tue, 13 Oct 2020 13:26:02 +0300 Subject: [PATCH 036/123] fix "selected graph changed after recalculation" --- .../plus/measurementtool/MtGraphFragment.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java index f3b4bbcf8c..eadc5c6a7e 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java @@ -101,6 +101,15 @@ public class MtGraphFragment extends BaseCard return R.layout.fragment_measurement_tool_graph; } + @Override + public void onUpdateAdditionalInfo() { + if (!isRouteCalculating()) { + updateGraphData(); + refreshGraphTypesSelectionMenu(); + } + updateDataView(); + } + private void refreshGraphTypesSelectionMenu() { rvGraphTypesMenu.removeAllViews(); OsmandApplication app = getMyApplication(); @@ -112,7 +121,7 @@ public class MtGraphFragment extends BaseCard if (type.isCustom()) { item.setTitleColorId(activeColorId); } - if (type.hasData() || type.canBeCalculated) { + if (type.isAvailable()) { items.add(item); } } @@ -134,13 +143,6 @@ public class MtGraphFragment extends BaseCard adapter.notifyDataSetChanged(); } - @Override - public void onUpdateAdditionalInfo() { - updateGraphData(); - refreshGraphTypesSelectionMenu(); - updateDataView(); - } - private void setupVisibleGraphType(GraphType type) { visibleGraphType = type; updateDataView(); @@ -163,7 +165,7 @@ public class MtGraphFragment extends BaseCard } private void updateDataView() { - if (mtf.isProgressBarVisible()) { + if (isRouteCalculating()) { showProgressMessage(); } else if (visibleGraphType.hasData()) { showGraph(); @@ -358,6 +360,10 @@ public class MtGraphFragment extends BaseCard defaultRender, currentSearchRequest, defaultSearchRequest); } + private boolean isRouteCalculating() { + return mtf.isProgressBarVisible(); + } + private static class GraphType { private String title; private boolean isCustom; From 3ad7590844c09cf45993276bda40cabc76c23507 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Tue, 13 Oct 2020 14:30:00 +0300 Subject: [PATCH 037/123] PR fixes --- OsmAnd/res/values/strings.xml | 3 + .../{MtGraphFragment.java => GraphsCard.java} | 102 ++++++++---------- .../MeasurementToolFragment.java | 44 +++----- ...{MtPointsFragment.java => PointsCard.java} | 18 ++-- .../RouteDetailsFragment.java | 25 +++-- 5 files changed, 90 insertions(+), 102 deletions(-) rename OsmAnd/src/net/osmand/plus/measurementtool/{MtGraphFragment.java => GraphsCard.java} (77%) rename OsmAnd/src/net/osmand/plus/measurementtool/{MtPointsFragment.java => PointsCard.java} (68%) diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 22b7b37b2f..5f63c1dc0b 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,9 @@ Thx - Hardy --> + Wait for the route recalculation.\nGraph will be available after recalculation. + %1$s data available only on the roads, you need to calculate a route using “Route between points” to get it. + Graph Use 2-phase A* routing algorithm File is already imported in OsmAnd Logout successful diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java similarity index 77% rename from OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java rename to OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java index eadc5c6a7e..9a5cbeffba 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtGraphFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/GraphsCard.java @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.github.mikephil.charting.charts.HorizontalBarChart; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.ChartData; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; @@ -20,13 +21,13 @@ import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.SettingsBaseActivity; import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet; +import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; -import net.osmand.plus.render.MapRenderRepositories; +import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener; +import net.osmand.plus.routepreparationmenu.RouteDetailsFragment; import net.osmand.plus.routepreparationmenu.cards.BaseCard; -import net.osmand.render.RenderingRuleSearchRequest; -import net.osmand.render.RenderingRulesStorage; import net.osmand.router.RouteSegmentResult; -import net.osmand.router.RouteStatisticsHelper; import net.osmand.util.Algorithms; import static net.osmand.router.RouteStatisticsHelper.RouteStatistics; @@ -39,22 +40,22 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public class MtGraphFragment extends BaseCard - implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { +public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListener { private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp"; + private MeasurementEditingContext editingCtx; + private MeasurementToolFragment fragment; + + private GraphType visibleGraphType; + private List graphTypes = new ArrayList<>(); + private View commonGraphContainer; private View customGraphContainer; private View messageContainer; private LineChart commonGraphChart; private HorizontalBarChart customGraphChart; - private RecyclerView rvGraphTypesMenu; - - private MeasurementEditingContext editingCtx; - private GraphType visibleGraphType; - private List graphTypes = new ArrayList<>(); - private MeasurementToolFragment mtf; + private RecyclerView graphTypesMenu; private enum CommonGraphType { OVERVIEW(R.string.shared_string_overview, false), @@ -71,15 +72,15 @@ public class MtGraphFragment extends BaseCard final boolean canBeCalculated; } - public MtGraphFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { + public GraphsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) { super(mapActivity); - this.mtf = mtf; + this.fragment = fragment; } @Override protected void updateContent() { - if (mapActivity == null || mtf == null) return; - editingCtx = mtf.getEditingCtx(); + if (mapActivity == null || fragment == null) return; + editingCtx = fragment.getEditingCtx(); commonGraphContainer = view.findViewById(R.id.common_graphs_container); customGraphContainer = view.findViewById(R.id.custom_graphs_container); @@ -88,8 +89,8 @@ public class MtGraphFragment extends BaseCard customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart); updateGraphData(); - rvGraphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); - rvGraphTypesMenu.setLayoutManager( + graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view); + graphTypesMenu.setLayoutManager( new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false)); refreshGraphTypesSelectionMenu(); @@ -111,7 +112,7 @@ public class MtGraphFragment extends BaseCard } private void refreshGraphTypesSelectionMenu() { - rvGraphTypesMenu.removeAllViews(); + graphTypesMenu.removeAllViews(); OsmandApplication app = getMyApplication(); int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode); @@ -139,7 +140,7 @@ public class MtGraphFragment extends BaseCard } } }); - rvGraphTypesMenu.setAdapter(adapter); + graphTypesMenu.setAdapter(adapter); adapter.notifyDataSetChanged(); } @@ -192,13 +193,13 @@ public class MtGraphFragment extends BaseCard commonGraphContainer.setVisibility(View.GONE); customGraphContainer.setVisibility(View.VISIBLE); messageContainer.setVisibility(View.GONE); - prepareCustomGraphView((BarData) visibleGraphType.getData()); + prepareCustomGraphView((BarData) visibleGraphType.getGraphData()); } else { commonGraphChart.clear(); commonGraphContainer.setVisibility(View.VISIBLE); customGraphContainer.setVisibility(View.GONE); messageContainer.setVisibility(View.GONE); - prepareCommonGraphView((LineData) visibleGraphType.getData()); + prepareCommonGraphView((LineData) visibleGraphType.getGraphData()); } } @@ -240,7 +241,7 @@ public class MtGraphFragment extends BaseCard // update common graph data for (CommonGraphType commonType : CommonGraphType.values()) { List dataSets = getDataSets(commonType, commonGraphChart, analysis); - Object data = null; + LineData data = null; if (!Algorithms.isEmpty(dataSets)) { data = new LineData(dataSets); } @@ -282,24 +283,24 @@ public class MtGraphFragment extends BaseCard OsmandApplication app = getMyApplication(); switch (type) { case OVERVIEW: { - List dataList = new ArrayList<>(); + List dataList = new ArrayList<>(); if (analysis.hasSpeedData) { - GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, true, true, false); + OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, true, true, false); dataList.add(speedDataSet); } if (analysis.hasElevationData) { - GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false); + OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, false, true, false); dataList.add(elevationDataSet); - GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); + OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false); dataList.add(slopeDataSet); } if (dataList.size() > 0) { - Collections.sort(dataList, new Comparator() { + Collections.sort(dataList, new Comparator() { @Override - public int compare(GpxUiHelper.OrderedLineDataSet o1, GpxUiHelper.OrderedLineDataSet o2) { + public int compare(OrderedLineDataSet o1, OrderedLineDataSet o2) { return Float.compare(o1.getPriority(), o2.getPriority()); } }); @@ -309,23 +310,23 @@ public class MtGraphFragment extends BaseCard } case ALTITUDE: { if (analysis.hasElevationData) { - GpxUiHelper.OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); + OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); dataSets.add(elevationDataSet); } break; } case SLOPE: if (analysis.hasElevationData) { - GpxUiHelper.OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, null, true, true, false); + OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false); dataSets.add(slopeDataSet); } break; case SPEED: { if (analysis.hasSpeedData) { - GpxUiHelper.OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, - analysis, GpxUiHelper.GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); + OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart, + analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps); dataSets.add(speedDataSet); } break; @@ -348,33 +349,24 @@ public class MtGraphFragment extends BaseCard private List calculateRouteStatistics(List route) { OsmandApplication app = getMyApplication(); if (route == null || app == null) return null; - - RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); - RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); - MapRenderRepositories maps = app.getResourceManager().getRenderer(); - RenderingRuleSearchRequest currentSearchRequest = - maps.getSearchRequestWithAppliedCustomRules(currentRenderer, nightMode); - RenderingRuleSearchRequest defaultSearchRequest = - maps.getSearchRequestWithAppliedCustomRules(defaultRender, nightMode); - return RouteStatisticsHelper.calculateRouteStatistic(route, currentRenderer, - defaultRender, currentSearchRequest, defaultSearchRequest); + return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode); } private boolean isRouteCalculating() { - return mtf.isProgressBarVisible(); + return fragment.isProgressBarVisible(); } private static class GraphType { private String title; private boolean isCustom; private boolean canBeCalculated; - private Object data; + private ChartData graphData; - public GraphType(String title, boolean isCustom, boolean canBeCalculated, Object data) { + public GraphType(String title, boolean isCustom, boolean canBeCalculated, ChartData graphData) { this.title = title; this.isCustom = isCustom; this.canBeCalculated = canBeCalculated; - this.data = data; + this.graphData = graphData; } public String getTitle() { @@ -394,11 +386,11 @@ public class MtGraphFragment extends BaseCard } public boolean hasData() { - return getData() != null; + return getGraphData() != null; } - public Object getData() { - return data; + public ChartData getGraphData() { + return graphData; } } } diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index 99ee2a6ef9..c40e7b70ca 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -127,6 +127,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private Snackbar snackbar; private String fileName; + private AdditionalInfoType currentAdditionalInfoType; + private boolean wasCollapseButtonVisible; private boolean progressBarVisible; private boolean additionalInfoExpanded; @@ -141,7 +143,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private boolean portrait; private boolean nightMode; private int cachedMapPosition; - private AdditionalInfoType currentAdditionalInfoType; private MeasurementEditingContext editingCtx = new MeasurementEditingContext(); @@ -514,17 +515,25 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) { if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) { + MapActivity ma = getMapActivity(); + if (ma == null) return; currentAdditionalInfoType = type; updateUpDownBtn(); - OsmandApplication app = getMyApplication(); - if (AdditionalInfoType.POINTS.equals(type)) { + OsmandApplication app = ma.getMyApplication(); + BaseCard additionalInfoCard = null; + if (AdditionalInfoType.POINTS == type) { + additionalInfoCard = new PointsCard(ma, this); UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START); - } else if (AdditionalInfoType.GRAPH.equals(type)) { + } else if (AdditionalInfoType.GRAPH == type) { + additionalInfoCard = new GraphsCard(ma, this); UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END); - } else { - return; } - setAdditionalInfoCard(type); + if (additionalInfoCard != null) { + visibleAdditionalInfoCard = additionalInfoCard; + additionalInfoCardsContainer.removeAllViews(); + additionalInfoCardsContainer.addView(additionalInfoCard.build(ma)); + additionalInfoExpanded = true; + } } } @@ -1477,25 +1486,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route } } - private void setAdditionalInfoCard(AdditionalInfoType type) { - MapActivity ma = getMapActivity(); - if (ma == null) return; - - BaseCard additionalInfoCard = null; - if (type.equals(AdditionalInfoType.POINTS)) { - additionalInfoCard = new MtPointsFragment(ma, this); - } else if (type.equals(AdditionalInfoType.GRAPH)) { - additionalInfoCard = new MtGraphFragment(ma, this); - } - - if (additionalInfoCard != null) { - visibleAdditionalInfoCard = additionalInfoCard; - additionalInfoCardsContainer.removeAllViews(); - additionalInfoCardsContainer.addView(additionalInfoCard.build(ma)); - additionalInfoExpanded = true; - } - } - private void collapseAdditionalInfoIfNoPointsEnough() { MeasurementToolLayer measurementLayer = getMeasurementLayer(); if (measurementLayer != null) { @@ -1515,7 +1505,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route setMapPosition(OsmandSettings.CENTER_CONSTANT); } - public void setMapPosition(int position) { + private void setMapPosition(int position) { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { mapActivity.getMapView().setMapPosition(position); diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java similarity index 68% rename from OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java rename to OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java index c859d9b84d..4b0117bd42 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MtPointsFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/PointsCard.java @@ -7,21 +7,19 @@ import androidx.recyclerview.widget.RecyclerView; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener; import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter; import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback; -public class MtPointsFragment extends BaseCard - implements MeasurementToolFragment.OnUpdateAdditionalInfoListener { +public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListener { private MeasurementToolAdapter adapter; - private MeasurementEditingContext editingCtx; - private RecyclerView pointsRv; - private MeasurementToolFragment mtf; + private MeasurementToolFragment fragment; - public MtPointsFragment(@NonNull MapActivity mapActivity, MeasurementToolFragment mtf) { + public PointsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) { super(mapActivity); - this.mtf = mtf; + this.fragment = fragment; } @Override @@ -36,14 +34,14 @@ public class MtPointsFragment extends BaseCard @Override protected void updateContent() { - editingCtx = mtf.getEditingCtx(); + MeasurementEditingContext editingCtx = fragment.getEditingCtx(); final GpxData gpxData = editingCtx.getGpxData(); adapter = new MeasurementToolAdapter(mapActivity, editingCtx.getPoints(), gpxData != null ? gpxData.getActionType() : null); - pointsRv = view.findViewById(R.id.measure_points_recycler_view); + RecyclerView pointsRv = view.findViewById(R.id.measure_points_recycler_view); ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter)); touchHelper.attachToRecyclerView(pointsRv); - adapter.setAdapterListener(mtf.createMeasurementAdapterListener(touchHelper)); + adapter.setAdapterListener(fragment.createMeasurementAdapterListener(touchHelper)); pointsRv.setLayoutManager(new LinearLayoutManager(app)); pointsRv.setAdapter(adapter); } diff --git a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java index fcab4d6578..1f29dfd1b7 100644 --- a/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java +++ b/OsmAnd/src/net/osmand/plus/routepreparationmenu/RouteDetailsFragment.java @@ -345,18 +345,9 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT List route = app.getRoutingHelper().getRoute().getOriginalRoute(); if (route != null) { - RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); - RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); - - MapRenderRepositories maps = app.getResourceManager().getRenderer(); - RenderingRuleSearchRequest currentSearchRequest = maps.getSearchRequestWithAppliedCustomRules(currentRenderer, isNightMode()); - RenderingRuleSearchRequest defaultSearchRequest = maps.getSearchRequestWithAppliedCustomRules(defaultRender, isNightMode()); - - List routeStatistics = RouteStatisticsHelper.calculateRouteStatistic(route, - currentRenderer, defaultRender, currentSearchRequest, defaultSearchRequest); + List routeStatistics = calculateRouteStatistics(app, route, isNightMode()); GPXTrackAnalysis analysis = gpx.getAnalysis(0); - for (RouteStatistics statistic : routeStatistics) { RouteInfoCard routeClassCard = new RouteInfoCard(mapActivity, statistic, analysis); addRouteCard(cardsContainer, routeClassCard); @@ -375,6 +366,20 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT } } + public static List calculateRouteStatistics(OsmandApplication app, + List route, + boolean nightMode) { + RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer(); + RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender(); + MapRenderRepositories maps = app.getResourceManager().getRenderer(); + RenderingRuleSearchRequest currentSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(currentRenderer, nightMode); + RenderingRuleSearchRequest defaultSearchRequest = + maps.getSearchRequestWithAppliedCustomRules(defaultRender, nightMode); + return RouteStatisticsHelper.calculateRouteStatistic(route, currentRenderer, + defaultRender, currentSearchRequest, defaultSearchRequest); + } + @Override protected void calculateLayout(View view, boolean initLayout) { super.calculateLayout(view, initLayout); From 71bd0beeeecd134ceff626d386ef1818bd34e4ed Mon Sep 17 00:00:00 2001 From: Eugene <44466116+EugeneZmeuk@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:03:03 +0300 Subject: [PATCH 038/123] Update RendererRegistry.java --- OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index 3f8ed81ba7..e2e2cfa6f9 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -46,6 +46,7 @@ public class RendererRegistry { public final static String LIGHTRS_RENDER = "LightRS"; //$NON-NLS-1$ public final static String UNIRS_RENDER = "UniRS"; //$NON-NLS-1$ public final static String DESERT_RENDER = "Desert"; //$NON-NLS-1$ + public final static String SNOWMOBILE_RENDER = "Snowmobile"; //$NON-NLS-1$ private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; From acd843dfb12409221e30feb6a24d88c435c0f857 Mon Sep 17 00:00:00 2001 From: Eugene <44466116+EugeneZmeuk@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:03:57 +0300 Subject: [PATCH 039/123] Update RendererRegistry.java --- OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index e2e2cfa6f9..96a12860b1 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -46,7 +46,6 @@ public class RendererRegistry { public final static String LIGHTRS_RENDER = "LightRS"; //$NON-NLS-1$ public final static String UNIRS_RENDER = "UniRS"; //$NON-NLS-1$ public final static String DESERT_RENDER = "Desert"; //$NON-NLS-1$ - public final static String SNOWMOBILE_RENDER = "Snowmobile"; //$NON-NLS-1$ private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; @@ -76,6 +75,7 @@ public class RendererRegistry { internalRenderers.put(WINTER_SKI_RENDER, "skimap" + ".render.xml"); internalRenderers.put(OFFROAD_RENDER, "offroad" + ".render.xml"); internalRenderers.put(DESERT_RENDER, "desert" + ".render.xml"); + internalRenderers.put(SNOWMOBILE_RENDER, "snowmobile" + ".render.xml"); } public RenderingRulesStorage defaultRender() { From f5daae62aff7d6a6d5965fdef056edea64e9531b Mon Sep 17 00:00:00 2001 From: Eugene <44466116+EugeneZmeuk@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:05:12 +0300 Subject: [PATCH 040/123] Update RendererRegistry.java --- OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index 96a12860b1..9fa1cb75be 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -46,6 +46,7 @@ public class RendererRegistry { public final static String LIGHTRS_RENDER = "LightRS"; //$NON-NLS-1$ public final static String UNIRS_RENDER = "UniRS"; //$NON-NLS-1$ public final static String DESERT_RENDER = "Desert"; //$NON-NLS-1$ + public final static String SNOWMOBILE_RENDER = "Snowmobile"; //$NON-NLS-1$ private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; From 7d1d225e2a7c66cf8692673b177a01e10b425a23 Mon Sep 17 00:00:00 2001 From: Eugene <44466116+EugeneZmeuk@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:20:34 +0300 Subject: [PATCH 041/123] Update strings.xml --- OsmAnd/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 5f63c1dc0b..a30f9c4fc5 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -1019,6 +1019,7 @@ Bookmark Hide full description Show full description + For snowmobile driving with dedicated roads and tracks. For off-road driving based on \'Topo\' style and for use with green satellite images as an underlay. Reduced main road thickness, increased thickness of tracks, paths, bicycle and other routes. For nautical navigation. Features buoys, lighthouses, riverways, sea lanes and marks, harbors, seamark services, and depth contours. For skiing. Features pistes, ski-lifts, cross country tracks, etc. Dims secondary map objects. From 3854e8d4be653ce23d03a9d0e24e4fe0cdab5772 Mon Sep 17 00:00:00 2001 From: Eugene <44466116+EugeneZmeuk@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:21:46 +0300 Subject: [PATCH 042/123] Update RendererRegistry.java --- OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index 9fa1cb75be..1b070e38a1 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -325,6 +325,8 @@ public class RendererRegistry { return ctx.getString(R.string.off_road_render_descr); case DESERT_RENDER: return ctx.getString(R.string.desert_render_descr); + case SNOWMOBILE_RENDER: + return ctx.getString(R.string.snowmobile_render_descr); } return ""; } From 6f80a83e01068ea50c2d9f4e6bf4f06530c7b135 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 13 Oct 2020 19:27:32 +0300 Subject: [PATCH 043/123] Add AIDL isFragmentOpen isMenuOpen --- .../osmand/aidlapi/IOsmAndAidlInterface.aidl | 10 +++++++++ OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java | 8 +++++++ .../net/osmand/aidl/OsmandAidlServiceV2.java | 22 +++++++++++++++++++ .../osmand/plus/activities/MapActivity.java | 17 ++++++++++---- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl index 72a3035b60..e25e4338de 100644 --- a/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl +++ b/OsmAnd-api/src/net/osmand/aidlapi/IOsmAndAidlInterface.aidl @@ -873,4 +873,14 @@ interface IOsmAndAidlInterface { 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/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index 5801fc916d..bad1f3998e 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -2317,6 +2317,14 @@ public class OsmandAidlApi { return false; } + public boolean isFragmentOpen() { + return mapActivity.isFragmentVisible(); + } + + public boolean isMenuOpen() { + return mapActivity.getContextMenu().isVisible(); + } + private static class FileCopyInfo { long startTime; long lastAccessTime; diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java index b904ef0517..78e7875d24 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlServiceV2.java @@ -1334,6 +1334,28 @@ public class OsmandAidlServiceV2 extends Service implements AidlCallbackListener return false; } } + + @Override + public boolean isFragmentOpen() { + try { + OsmandAidlApi api = getApi("isFragmentOpen"); + return api != null && api.isFragmentOpen(); + } catch (Exception e) { + handleException(e); + return false; + } + } + + @Override + public boolean isMenuOpen() { + try { + OsmandAidlApi api = getApi("isMenuOpen"); + return api != null && api.isMenuOpen(); + } catch (Exception e) { + handleException(e); + return false; + } + } }; private void setCustomization(OsmandAidlApi api, CustomizationInfoParams params) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 3c63073d89..77acb11bf0 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -74,6 +74,7 @@ import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationSimulation; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.dashboard.DashBaseFragment; import net.osmand.plus.helpers.DayNightHelper; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings; @@ -1029,15 +1030,23 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven } public boolean isMapVisible() { - for (Fragment fragment : getSupportFragmentManager().getFragments()) { - if (fragment.isVisible()) { - return false; - } + if (isFragmentVisible()) { + return false; } return AndroidUtils.isActivityNotDestroyed(this) && settings.MAP_ACTIVITY_ENABLED.get() && !dashboardOnMap.isVisible(); } + public boolean isFragmentVisible() { + for (Fragment fragment : getSupportFragmentManager().getFragments()) { + if (!(fragment instanceof DashBaseFragment) && fragment.isVisible() + || dashboardOnMap.isVisible()) { + return true; + } + } + return false; + } + private void restartApp() { AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(R.string.storage_permission_restart_is_required); From 4d9efabf082a23471d90f3976764cd032bd5e1ce Mon Sep 17 00:00:00 2001 From: Hakim Oubouali Date: Tue, 13 Oct 2020 20:22:30 +0200 Subject: [PATCH 044/123] Added translation using Weblate (Central Atlas Tamazight) --- OsmAnd/res/values-tzm/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 OsmAnd/res/values-tzm/strings.xml diff --git a/OsmAnd/res/values-tzm/strings.xml b/OsmAnd/res/values-tzm/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/OsmAnd/res/values-tzm/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 32cb769e4e4bc336a824d74915183b6d8b9ad511 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 13 Oct 2020 17:28:31 +0000 Subject: [PATCH 045/123] Translated using Weblate (Portuguese) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-pt/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index 12f7810ed8..c3292ad2e7 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -3928,4 +3928,6 @@ Fazer login via OAuth Limpar token do OpenStreetMap OAuth Logout bem sucedido + O ficheiro já é importado em OsmAnd + Usar algoritmo de roteamento de 2 fases A* \ No newline at end of file From 2a1046b4c6bd6cc2f8fc9f34d559c27846e47967 Mon Sep 17 00:00:00 2001 From: Hinagiku Zeppeki Date: Tue, 13 Oct 2020 11:42:14 +0000 Subject: [PATCH 046/123] Translated using Weblate (Japanese) Currently translated at 98.5% (3454 of 3506 strings) --- OsmAnd/res/values-ja/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index 16d59f00aa..c55ee475f1 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -3912,4 +3912,6 @@ POIの更新は利用できません OsmAnd Liveデータ 複雑なルート計算 カーナビゲーション向けの2段階ルート計算です。 + 2段階 A*ルーティングアルゴリズムを使用 + ファイルはすでにOsmAndにインポートされています \ No newline at end of file From b40c25af86fb682914ccfb84c167acbc472fc7fe Mon Sep 17 00:00:00 2001 From: Ldm Public Date: Tue, 13 Oct 2020 06:17:45 +0000 Subject: [PATCH 047/123] Translated using Weblate (French) Currently translated at 99.9% (3505 of 3506 strings) --- OsmAnd/res/values-fr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 7df4716103..3b52399258 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3900,4 +3900,6 @@ Connectez-vous avec OAuth Supprimer le jeton OAuth d\'OpenStreetMap Déconnexion réussie + Le fichier est déjà importé dans OsmAnd + Utiliser un algorithme de routage A* à 2 phases \ No newline at end of file From d524d7c2a9a22faafb3e413e2186a431c68648ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 13 Oct 2020 09:49:30 +0000 Subject: [PATCH 048/123] Translated using Weblate (Turkish) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-tr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 07ec369a18..d0aa6a6e35 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -3881,4 +3881,6 @@ OAuth ile oturum aç OpenStreetMap OAuth belirtecini temizle Oturum kapatma başarılı + Dosya zaten OsmAnd\'da içe aktarıldı + 2 aşamalı A* yönlendirme algoritması kullan \ No newline at end of file From 72e182986bc7872f358428e5cc44e4f8bf4de23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Babos=20G=C3=A1bor?= Date: Tue, 13 Oct 2020 07:54:39 +0000 Subject: [PATCH 049/123] Translated using Weblate (Hungarian) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-hu/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index 1dd28463f8..ac040cd72f 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -3918,4 +3918,6 @@ Bejelentkezés OAuth segítségével OpenStreetMap OAuth token törlése Sikeresen kijelentkezett + Kétszakaszos A* útvonaltervezési algoritmus használata + A fájl már importálva van az OsmAndba \ No newline at end of file From d771d61eaad7b3fc8eab4ea2543a508e972e57e8 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 13 Oct 2020 15:08:51 +0000 Subject: [PATCH 050/123] Translated using Weblate (Ukrainian) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-uk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 8ba9a9906f..5a20046ed1 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -3922,4 +3922,6 @@ Уникати пішохідних шляхів Уникати пішохідних шляхів Розробка + Файл уже імпортовано до OsmAnd + Використання 2-фазного A* алгоритму маршрутизації \ No newline at end of file From f830e8b32f2ffe48567898f0bcf7cc15aa1f70bb Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 13 Oct 2020 05:12:46 +0000 Subject: [PATCH 051/123] Translated using Weblate (Hebrew) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-iw/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 756dfe73dd..2c210a5c47 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3931,4 +3931,6 @@ להיכנס דרך OAuth למחוק את אסימון ה־OAuth של OpenStreetMap היציאה הצליחה + הקובץ כבר ייובא אל OsmAnd + להשתמש באלגוריתם חישוב מסלול דו־שלבי A*‎ \ No newline at end of file From 6edbf12372ed2d1c47e48f4e716bef0084a111e6 Mon Sep 17 00:00:00 2001 From: Zmicer Turok Date: Tue, 13 Oct 2020 04:24:10 +0000 Subject: [PATCH 052/123] Translated using Weblate (Belarusian) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-be/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OsmAnd/res/values-be/strings.xml b/OsmAnd/res/values-be/strings.xml index fa503e2464..3d0aa6103d 100644 --- a/OsmAnd/res/values-be/strings.xml +++ b/OsmAnd/res/values-be/strings.xml @@ -3971,4 +3971,13 @@ Дзвюхфазная аўтанавігацыя. Натыўны грамадскі транспарт (у распрацоўцы) Увайсці праз OAuth + Выкарыстоўваць 2-фазны алгарытм маршрутызацыі A * + Перайсці на разлік маршруту грамадскага транспарту на Java (бяспечны) + Файл ужо імпартаваны ў OsmAnd + Значкі старту і фінішу + Увайдзіце праз OAuth, каб выкарыстоўваць функцыі osmedit + Ачысціць токен OpenStreetMap OAuth + Выхад выкананы + Даныя OsmAnd Live + Даныя OsmAnd Live \ No newline at end of file From 89e0ef65ac18fa60b2fe59214f110a05f4d9330e Mon Sep 17 00:00:00 2001 From: Ahmad Alfrhood Date: Tue, 13 Oct 2020 10:28:56 +0000 Subject: [PATCH 053/123] Translated using Weblate (Arabic) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-ar/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index 3e067c722c..32c555c423 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -3913,4 +3913,6 @@ تسجيل الدخول عبر OAuth مسح رمز OpenStreetMap OAuth تسجيل الخروج بنجاح + تم استيراد الملف بالفعل في أوسماند + استخدام خوارزمية توجيه من مرحلتين A* \ No newline at end of file From 9ce3582dff7ac49863979c440881f3cc665b26f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 13 Oct 2020 05:49:18 +0000 Subject: [PATCH 054/123] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 24.6% (865 of 3506 strings) --- OsmAnd/res/values-nb/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index 97364e7f2d..85bfc9b423 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -3866,4 +3866,6 @@ Logg inn via OAuth Hva er nytt Utlogget + Bruk 2-stegs A*-rutingsalgoritme + Filen er allerede importert i OsmAnd \ No newline at end of file From 1eb0f4b3b0e1969bfb9fbfd15b3fbbd299be374f Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Tue, 13 Oct 2020 15:15:41 +0000 Subject: [PATCH 055/123] Translated using Weblate (Sardinian) Currently translated at 99.6% (3493 of 3506 strings) --- OsmAnd/res/values-sc/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index ac6f4f667a..3e7f0b8fb7 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -3913,4 +3913,17 @@ \nPodes amministrare e annullare sos abbonamentos tuos intrende in sas impostatziones de AppGallery tuas. Èvita sos martzapiedis Èvita sos martzapiedis + Ite b\'at de nou + Isvilupu + Datos de OsmAnd Live + Datos de OsmAnd Live + Càrculu de s\'àndala a duas fases pro sa navigatzione in màchina. + Isvilupu de sos trasportos pùblicos nativos + Cola a su càrculu de s\'àndala de sos trasportos pùblicos Java (seguru) + Intra cun OAuth pro impreare sas funtzionalidades osmedit + Intra impreende OAuth + Iscantzella su getone OAuth de OpenStreetMap + Essida fata chene problemas + Su documentu est giai importadu in OsmAnd + Imprea un\'algoritmu de càrculu de s\'àndala A* a duas fases \ No newline at end of file From 1f4508d896dcf22491e70f0bf04c1c0ad14a55a3 Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Tue, 13 Oct 2020 15:09:06 +0000 Subject: [PATCH 056/123] Translated using Weblate (Sardinian) Currently translated at 99.6% (3812 of 3825 strings) --- OsmAnd/res/values-sc/phrases.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values-sc/phrases.xml b/OsmAnd/res/values-sc/phrases.xml index 62f4c1ece8..e0a15ffcdb 100644 --- a/OsmAnd/res/values-sc/phrases.xml +++ b/OsmAnd/res/values-sc/phrases.xml @@ -3557,20 +3557,20 @@ Radioterapia Perìgulu Categoria de dificultade - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Frama de gas;Tortza de brusiadura Ogetu iscantzelladu Radioterapia From 5e6c0d1d60566e030a7d63143f3e3e829b3391a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Babos=20G=C3=A1bor?= Date: Tue, 13 Oct 2020 07:53:03 +0000 Subject: [PATCH 057/123] Translated using Weblate (Hungarian) Currently translated at 99.9% (3822 of 3825 strings) --- OsmAnd/res/values-hu/phrases.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values-hu/phrases.xml b/OsmAnd/res/values-hu/phrases.xml index 0234cd76a2..eb5f1ef453 100644 --- a/OsmAnd/res/values-hu/phrases.xml +++ b/OsmAnd/res/values-hu/phrases.xml @@ -3553,20 +3553,20 @@ Sugárkezelés Veszély Nehézségi fok (az Orosz Túrasportszövetség skáláján) - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + N/A + N/A* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gázfáklya Törölt objektum Sugárkezelés From a237781417fdf66587d6fef8d5edcbac9ab20319 Mon Sep 17 00:00:00 2001 From: Zmicer Turok Date: Tue, 13 Oct 2020 04:21:18 +0000 Subject: [PATCH 058/123] Translated using Weblate (Belarusian) Currently translated at 99.6% (3810 of 3825 strings) --- OsmAnd/res/values-be/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-be/phrases.xml b/OsmAnd/res/values-be/phrases.xml index f96eaef3ab..253f7eb98d 100644 --- a/OsmAnd/res/values-be/phrases.xml +++ b/OsmAnd/res/values-be/phrases.xml @@ -3844,4 +3844,5 @@ Маленькія электрапрыборы Вулей Крама арэхаў + \ No newline at end of file From 8482854c862c2066e8a55adbf9f3eaa09c025439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 13 Oct 2020 05:12:33 +0000 Subject: [PATCH 059/123] Translated using Weblate (Estonian) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-et/phrases.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values-et/phrases.xml b/OsmAnd/res/values-et/phrases.xml index 4cb42eee67..daafc22ac5 100644 --- a/OsmAnd/res/values-et/phrases.xml +++ b/OsmAnd/res/values-et/phrases.xml @@ -3559,20 +3559,20 @@ $0.5 mündid Laadimisjaam Oht - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + k/p + k/p* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* Gaasi põletamine;Hõõglamp Kustutatud objekt Kiiritusravi From 7c4aeb152945d8090c04b995855f6d54ae505250 Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Tue, 13 Oct 2020 14:54:00 +0000 Subject: [PATCH 060/123] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3506 of 3506 strings) --- OsmAnd/res/values-pt-rBR/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index 514414ef4c..5b99d1ed99 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -3921,4 +3921,6 @@ Entrar via OAuth Limpar token do OpenStreetMap OAuth Saída bem sucedida + O arquivo já foi importado para OsmAnd + Use o algoritmo de roteamento 2-phase A * \ No newline at end of file From c6190f597056983a18a92efc9bd0f9cce36834d3 Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Tue, 13 Oct 2020 15:02:23 +0000 Subject: [PATCH 061/123] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-pt-rBR/phrases.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OsmAnd/res/values-pt-rBR/phrases.xml b/OsmAnd/res/values-pt-rBR/phrases.xml index 07c80c478d..bcb7d4a63f 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -3561,14 +3561,14 @@ Radioterapia Perigo Categoria de dificuldade - н/к - н/к* - - 1А* + n/c + n/c* + 1A + 1A* 1B 1B* - 2А* + 2A* 2B 2B* From 9d4f67c68ea04b9e4531b568b9876219b31d7410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 13 Oct 2020 05:17:29 +0000 Subject: [PATCH 062/123] Translated using Weblate (Estonian) Currently translated at 99.3% (3483 of 3506 strings) --- OsmAnd/res/values-et/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OsmAnd/res/values-et/strings.xml b/OsmAnd/res/values-et/strings.xml index fdd760f307..8ccdb58038 100644 --- a/OsmAnd/res/values-et/strings.xml +++ b/OsmAnd/res/values-et/strings.xml @@ -3775,4 +3775,11 @@ Arveldame tellimuse eest valitud ajavahemiku alusel. Seda saad sa vabalt valitud ajal tühistada AppGallery\'s. Keeruka teekonna koostamine Väljalogimine õnnestus + Arendus + Kustuta OpenStreetMap\'i OAuth\'i pääsuluba + Logi sisse OAuth abil + Kui sa soovid kasutada kaardi muutmise võimalusi, siis palun logi sisse OAuth abil + Meie uudised + Kasuta kahefaasilist A-klassi teekonna koostamise algoritmi + See fail on juba OsmAnd\'i imporditud \ No newline at end of file From cf5d3c9df432e6e12a2c5bbdb97118ccfe981cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 13 Oct 2020 18:22:10 +0000 Subject: [PATCH 063/123] Translated using Weblate (Turkish) Currently translated at 79.3% (3036 of 3825 strings) --- OsmAnd/res/values-tr/phrases.xml | 56 +++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index 99b07f5d00..0353ad6b71 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -1344,7 +1344,7 @@ Ziraat malzemeleri Döşeme malzemeleri Nüfus - Yeraltı + Yer altı Çok katlı Standlar Duvar döngüleri @@ -2804,15 +2804,15 @@ Sağlık çalışanının rolü: psikolog İlk yardım çantası Duvar - Yeraltı + Yer altı Sağlık merkezi türü: sahra hastanesi Sağlık merkezi türü: laboratuvar Sağlık hizmeti: aşılama: hayır Sağlık hizmeti: aşılama: evet Sağlık hizmeti: danışma: hayır Sağlık hizmeti: danışma: evet - Sağlık hizmeti: hasta bakıcılık: hayır - Sağlık hizmeti: hasta bakıcılık: evet + Sağlık hizmeti: hemşirelik: hayır + Sağlık hizmeti: hemşirelik: evet Geleneksel Tibet Geleneksel Moğol Geleneksel Çin @@ -2999,4 +2999,52 @@ Bandy Sokak dolabı Ateş çukuru + Ana + Musluk tarzı: wsh + Yer altı + Sokak + Park yeri + Şerit + Çimen + Kaldırım + Musluk akış kapasitesi + Musluk sayısı + Musluk basıncı + Musluk çapı + Ebe ofisi + Hemşirelik hizmeti + Psikolog ofisi + Şifacı ofisi + Podolog ofisi + Terapist ofisi + Doktor ofisi + Yatarak tedavi hizmetleri: yalnızca + Yatarak tedavi hizmetleri: hayır + Yatarak tedavi hizmetleri: evet + Danışma (şiddet): hayır + Danışma (şiddet): evet + Danışma (kurban): hayır + Danışma (kurban): evet + Danışma (cinsel istismar): hayır + Danışma (cinsel istismar): evet + Danışma (cinsellik): hayır + Danışma (cinsellik): evet + Danışma (rehabilitasyon): hayır + Danışma (rehabilitasyon): evet + Danışma (beslenme): hayır + Danışma (beslenme): evet + Danışma (evlilik): hayır + Danışma (evlilik): evet + Danışma (göçmen): hayır + Danışma (göçmen): evet + Danışma (eğitim): hayır + Danışma (eğitim): evet + Danışma (kriz): hayır + Danışma (kriz): evet + Danışma (çift): hayır + Danışma (çift): evet + Danışma (çocuk rehberliği): hayır + Danışma (çocuk rehberliği): evet + Danışma (doğum öncesi): evet + Danışma (doğum öncesi): hayır \ No newline at end of file From 28c48f394f34c315ab69d73748285b1062d06f86 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 13 Oct 2020 17:24:45 +0000 Subject: [PATCH 064/123] Translated using Weblate (Portuguese) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-pt/phrases.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OsmAnd/res/values-pt/phrases.xml b/OsmAnd/res/values-pt/phrases.xml index fc6f81cb62..05bf64827a 100644 --- a/OsmAnd/res/values-pt/phrases.xml +++ b/OsmAnd/res/values-pt/phrases.xml @@ -3548,18 +3548,18 @@ Bonde Balsa Fonte de energia: biomassa - н/к - н/к* - - 1А* + n/c + n/c* + 1A + 1A* 1B 1B* - - 2А* + 2A + 2A* 2B 2B* - - 3А* + 3A + 3A* 3B 3B* Explosão de gás;Queimador de gás From 5fb99552235612ed69237bd4edeb7aa27ab7a923 Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Wed, 14 Oct 2020 05:24:04 +0000 Subject: [PATCH 065/123] Translated using Weblate (Russian) Currently translated at 99.7% (3500 of 3510 strings) --- OsmAnd/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 50a4f8b4f6..365e17ee82 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3920,4 +3920,5 @@ Войти через OAuth Очистить токен OAuth OpenStreetMap Выход выполнен + »в 997777777:66666776666 \ No newline at end of file From ef1b3c1398142edabb2b5939e16c0571fe1758ec Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 14 Oct 2020 12:15:02 +0300 Subject: [PATCH 066/123] Update target sdk initial commit --- OsmAnd/AndroidManifest.xml | 3 ++- OsmAnd/build.gradle | 2 +- OsmAnd/src/net/osmand/plus/OsmandPlugin.java | 10 +++++++++- .../net/osmand/plus/activities/MapActivity.java | 17 ++++++++++++----- .../plus/audionotes/AudioVideoNotesPlugin.java | 2 +- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 4e5395a04a..d762e0a261 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -53,7 +53,8 @@ android:icon="@mipmap/icon" android:label="@string/app_name" android:name="net.osmand.plus.OsmandApplication" android:configChanges="locale" android:theme="@style/OsmandDarkTheme" android:restoreAnyVersion="true" android:largeHeap="true" - android:supportsRtl="true" android:usesCleartextTraffic="true"> + android:supportsRtl="true" android:usesCleartextTraffic="true" + android:hasFragileUserData="true" android:requestLegacyExternalStorage="true"> diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index c2361dd165..3638a103ca 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -53,7 +53,7 @@ android { defaultConfig { minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode multiDexEnabled true diff --git a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java index 4e9ca3d420..e86b64df43 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/OsmandPlugin.java @@ -46,7 +46,6 @@ import net.osmand.plus.quickaction.QuickActionType; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.settings.fragments.BaseSettingsFragment; @@ -499,6 +498,9 @@ public abstract class OsmandPlugin { public void mapActivityResume(MapActivity activity) { } + public void mapActivityResumeOnTop(MapActivity activity) { + } + public void mapActivityPause(MapActivity activity) { } @@ -752,6 +754,12 @@ public abstract class OsmandPlugin { } } + public static void onMapActivityResumeOnTop(MapActivity activity) { + for (OsmandPlugin plugin : getEnabledPlugins()) { + plugin.mapActivityResumeOnTop(activity); + } + } + public static void onMapActivityPause(MapActivity activity) { for (OsmandPlugin plugin : getEnabledPlugins()) { plugin.mapActivityPause(activity); diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 77acb11bf0..53d3fcf473 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -74,10 +74,6 @@ import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationSimulation; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.dashboard.DashBaseFragment; -import net.osmand.plus.helpers.DayNightHelper; -import net.osmand.plus.settings.backend.CommonPreference; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper; import net.osmand.plus.TargetPointsHelper.TargetPoint; @@ -88,6 +84,7 @@ import net.osmand.plus.base.ContextMenuFragment; import net.osmand.plus.base.FailSafeFuntions; import net.osmand.plus.base.MapViewTrackingUtilities; import net.osmand.plus.chooseplan.OsmLiveCancelledDialog; +import net.osmand.plus.dashboard.DashBaseFragment; import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment; import net.osmand.plus.dialogs.ImportGpxBottomSheetDialogFragment; @@ -101,13 +98,14 @@ import net.osmand.plus.download.ui.DataStoragePlaceDialogFragment; import net.osmand.plus.firstusage.FirstUsageWelcomeFragment; import net.osmand.plus.firstusage.FirstUsageWizardFragment; import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.DayNightHelper; import net.osmand.plus.helpers.DiscountHelper; -import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.helpers.IntentHelper; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.LockHelper.LockUIAdapter; import net.osmand.plus.helpers.ScrollHelper; import net.osmand.plus.helpers.ScrollHelper.OnScrollEventListener; +import net.osmand.plus.importfiles.ImportHelper; import net.osmand.plus.mapcontextmenu.AdditionalActionsBottomSheetDialogFragment; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.MenuController; @@ -133,7 +131,9 @@ import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchTab; import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchType; import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmAndAppCustomization.OsmAndAppCustomizationListener; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType; import net.osmand.plus.settings.fragments.ConfigureProfileFragment; @@ -886,6 +886,13 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven settings.USE_SYSTEM_SCREEN_TIMEOUT.addListener(useSystemScreenTimeoutListener); } + @Override + public void onTopResumedActivityChanged(boolean isTopResumedActivity) { + if (isTopResumedActivity) { + OsmandPlugin.onMapActivityResumeOnTop(this); + } + } + public void applyScreenOrientation() { if (settings.MAP_SCREEN_ORIENTATION.get() != getRequestedOrientation()) { setRequestedOrientation(settings.MAP_SCREEN_ORIENTATION.get()); diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java index a68a8bc749..0a99d15b03 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java @@ -893,7 +893,7 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } @Override - public void mapActivityResume(MapActivity activity) { + public void mapActivityResumeOnTop(MapActivity activity) { this.mapActivity = activity; // ((AudioManager) activity.getSystemService(Context.AUDIO_SERVICE)).registerMediaButtonEventReceiver( // new ComponentName(activity, MediaRemoteControlReceiver.class)); From 4c1ba0b15490f3b34423efd8292476da893447fe Mon Sep 17 00:00:00 2001 From: solokot Date: Wed, 14 Oct 2020 10:00:36 +0000 Subject: [PATCH 067/123] Translated using Weblate (Russian) Currently translated at 99.7% (3502 of 3510 strings) --- OsmAnd/res/values-ru/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 365e17ee82..616ee0c58c 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3580,7 +3580,7 @@ Примечание: проверка скорости > 0: большинство модулей GPS сообщают значение скорости только в том случае, если алгоритм определяет, что вы движетесь, и ничего, если вы не перемещаетесь. Следовательно, использование параметра > 0 в этом фильтре в некотором смысле приводит к обнаружению факта перемещения модуля GPS. Но даже если мы не производим данную фильтрацию во время записи, то всё равно эта функция используется при анализе GPX для определения скорректированного расстояния, то есть значение, отображаемое в этом поле, является расстоянием, записанным во время движения. Разделение записи Укажите веб-адрес со следующими параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}. - "Будут записываться только точки, отвечающие по показателю минимальной точности (в метрах или футах — зависит от настроек системы). Точность — это близость измерений к истинному положению, и она не связана напрямую с точностью, которая представляет собой разброс повторных измерений." + Будут записываться только точки, отвечающие по показателю минимальной точности (в метрах или футах — зависит от настроек системы). Точность — это близость измерений к истинному положению, и она не связана напрямую с точностью, которая представляет собой разброс повторных измерений. Рекомендация: попробуйте сначала воспользоваться детектором движения через фильтр минимального смещения (B), что может дать лучшие результаты и вы потеряете меньше данных. Если треки остаются шумными на низких скоростях, попробуйте использовать ненулевые значения. Обратите внимание, что некоторые измерения могут вообще не указывать значения скорости (некоторые сетевые методы), и в этом случае ничего не будет записываться. Для визуализации крутизны рельефа используются цвета. Подробнее об уклонах можно прочитать в %1$s. @@ -3906,9 +3906,9 @@ Избегать пешеходных дорожек Избегать пешеходных дорожек Подписка взимается за выбранный период. Отмените её в AppGallery в любое время. - Оплата будет снята с вашей учетной записи AppGallery при подтверждении покупки. + Оплата будет снята с вашей учётной записи AppGallery при подтверждении покупки. \n -\nПодписка продлевается автоматически, если она не будет отменена до даты продления. С вашего счета будет взиматься плата за период продления (месяц/три месяца/год) только в дату продления. +\nПодписка продлевается автоматически, если она не будет отменена до даты продления. С вашего счёта будет взиматься плата за период продления (месяц/три месяца/год) только в дату продления. \n \nВы можете управлять своими подписками и отменять их, перейдя в настройки AppGallery. Данные OsmAnd Live @@ -3921,4 +3921,5 @@ Очистить токен OAuth OpenStreetMap Выход выполнен »в 997777777:66666776666 + Файл уже импортирован \ No newline at end of file From 9923430ac32b87fa9a1d82b0dbf4539d48995c03 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 14 Oct 2020 23:03:28 +0300 Subject: [PATCH 068/123] Move settings items to separate files --- OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java | 10 +- .../src/net/osmand/plus/AppInitializer.java | 2 +- .../net/osmand/plus/CustomOsmandPlugin.java | 18 +- .../net/osmand/plus/OsmandApplication.java | 2 +- .../osmand/plus/importfiles/ImportHelper.java | 4 +- .../plus/importfiles/SettingsImportTask.java | 12 +- .../plus/settings/backend/SettingsHelper.java | 3139 ----------------- .../backup/AvoidRoadsSettingsItem.java | 179 + .../backup/CollectionSettingsItem.java | 75 + .../backend/backup/DataSettingsItem.java | 87 + .../backend/backup/DownloadsItem.java | 102 + .../backend/backup/FileSettingsItem.java | 234 ++ .../backend/backup/GlobalSettingsItem.java | 65 + .../backup/MapSourcesSettingsItem.java | 241 ++ .../backend/backup/OsmandSettingsItem.java | 33 + .../backup/OsmandSettingsItemReader.java | 78 + .../backup/OsmandSettingsItemWriter.java | 49 + .../backend/backup/PluginSettingsItem.java | 116 + .../backup/PoiUiFiltersSettingsItem.java | 178 + .../backend/backup/ProfileSettingsItem.java | 312 ++ .../backup/QuickActionsSettingsItem.java | 183 + .../backend/backup/ResourcesSettingsItem.java | 72 + .../backend/backup/SettingsExporter.java | 91 + .../backend/backup/SettingsHelper.java | 614 ++++ .../backend/backup/SettingsImporter.java | 137 + .../settings/backend/backup/SettingsItem.java | 236 ++ .../backend/backup/SettingsItemReader.java | 17 + .../backend/backup/SettingsItemWriter.java | 21 + .../backend/backup/SettingsItemsFactory.java | 131 + .../backend/backup/StreamSettingsItem.java | 76 + .../backup/StreamSettingsItemReader.java | 10 + .../backup/StreamSettingsItemWriter.java | 34 + .../backup/SuggestedDownloadsItem.java | 128 + .../fragments/ConfigureProfileFragment.java | 13 +- .../fragments/ExportProfileBottomSheet.java | 6 +- .../fragments/ImportCompleteFragment.java | 4 +- .../fragments/ImportDuplicatesFragment.java | 8 +- .../fragments/ImportSettingsFragment.java | 22 +- .../fragments/MainSettingsFragment.java | 4 +- .../fragments/ProfileAppearanceFragment.java | 5 +- 40 files changed, 3556 insertions(+), 3192 deletions(-) delete mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index bad1f3998e..21ed74a437 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -81,8 +81,10 @@ import net.osmand.plus.routing.VoiceRouter; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.settings.backend.ExportSettingsType; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.layers.AidlMapLayer; @@ -138,7 +140,7 @@ import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DIRECTION_NAME; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DIRECTION_TURN; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_DISTANCE; import static net.osmand.plus.helpers.ExternalApiHelper.PARAM_NT_IMMINENT; -import static net.osmand.plus.settings.backend.SettingsHelper.REPLACE_KEY; +import static net.osmand.plus.settings.backend.backup.SettingsHelper.REPLACE_KEY; public class OsmandAidlApi { @@ -2305,8 +2307,8 @@ public class OsmandAidlApi { for (String key : settingsTypesKeys) { settingsTypes.add(ExportSettingsType.valueOf(key)); } - List settingsItems = new ArrayList<>(); - settingsItems.add(new SettingsHelper.ProfileSettingsItem(app, appMode)); + List settingsItems = new ArrayList<>(); + settingsItems.add(new ProfileSettingsItem(app, appMode)); File exportDir = app.getSettings().getExternalStorageDirectory(); String fileName = appMode.toHumanString(); SettingsHelper settingsHelper = app.getSettingsHelper(); diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 78364460a7..58690a9a3b 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -55,7 +55,7 @@ import net.osmand.plus.routing.TransportRoutingHelper; import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.views.corenative.NativeCoreContext; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.voice.CommandPlayerException; diff --git a/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java b/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java index e0cb743a64..9127fa0aef 100644 --- a/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java +++ b/OsmAnd/src/net/osmand/plus/CustomOsmandPlugin.java @@ -18,15 +18,15 @@ import net.osmand.data.LatLon; import net.osmand.map.ITileSource; import net.osmand.map.WorldRegion; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.AvoidRoadsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.MapSourcesSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PluginSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PoiUiFiltersSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.QuickActionsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.AvoidRoadsSettingsItem; +import net.osmand.plus.settings.backend.backup.MapSourcesSettingsItem; +import net.osmand.plus.settings.backend.backup.PluginSettingsItem; +import net.osmand.plus.settings.backend.backup.PoiUiFiltersSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.download.DownloadActivityType; import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadResources; diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 1a48650a4d..b57d94dc3a 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -75,7 +75,7 @@ import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.wikivoyage.data.TravelDbHelper; import net.osmand.router.GeneralRouter; diff --git a/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java b/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java index 1059a46bb0..1416e29e31 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/ImportHelper.java @@ -34,8 +34,8 @@ import net.osmand.plus.dialogs.ImportGpxBottomSheetDialogFragment; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; import net.osmand.plus.measurementtool.MeasurementToolFragment; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java b/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java index 55135832e5..d2d598d927 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/SettingsImportTask.java @@ -13,12 +13,12 @@ import net.osmand.FileUtils; import net.osmand.IndexConstants; import net.osmand.plus.CustomOsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper.CheckDuplicatesListener; -import net.osmand.plus.settings.backend.SettingsHelper.PluginSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsImportListener; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.CheckDuplicatesListener; +import net.osmand.plus.settings.backend.backup.PluginSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsImportListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.fragments.ImportSettingsFragment; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java deleted file mode 100644 index a708f92a46..0000000000 --- a/OsmAnd/src/net/osmand/plus/settings/backend/SettingsHelper.java +++ /dev/null @@ -1,3139 +0,0 @@ -package net.osmand.plus.settings.backend; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.AsyncTask; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import net.osmand.AndroidUtils; -import net.osmand.IndexConstants; -import net.osmand.PlatformUtil; -import net.osmand.data.LatLon; -import net.osmand.map.ITileSource; -import net.osmand.map.TileSourceManager; -import net.osmand.map.TileSourceManager.TileSourceTemplate; -import net.osmand.map.WorldRegion; -import net.osmand.osm.MapPoiTypes; -import net.osmand.osm.PoiCategory; -import net.osmand.plus.CustomOsmandPlugin; -import net.osmand.plus.CustomOsmandPlugin.SuggestedDownloadItem; -import net.osmand.plus.CustomRegion; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.SQLiteTileSource; -import net.osmand.plus.helpers.AvoidSpecificRoads; -import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; -import net.osmand.plus.poi.PoiUIFilter; -import net.osmand.plus.quickaction.QuickAction; -import net.osmand.plus.quickaction.QuickActionRegistry; -import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; -import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBuilder; -import net.osmand.router.GeneralRouter; -import net.osmand.util.Algorithms; - -import org.apache.commons.logging.Log; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -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; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; - -/* - Usage: - - SettingsHelper helper = app.getSettingsHelper(); - File file = new File(app.getAppPath(null), "settings.zip"); - - List items = new ArrayList<>(); - items.add(new GlobalSettingsItem(app.getSettings())); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.DEFAULT)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.CAR)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.PEDESTRIAN)); - items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.BICYCLE)); - items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 2.gpx"))); - items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 3.gpx"))); - items.add(new FileSettingsItem(app, new File(app.getAppPath(RENDERERS_DIR), "default.render.xml"))); - items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '1'}, "data1")); - items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '2'}, "data2")); - - helper.exportSettings(file, items); - - helper.importSettings(file); - */ - -public class SettingsHelper { - - public static final int VERSION = 1; - - public static final String SETTINGS_TYPE_LIST_KEY = "settings_type_list_key"; - public static final String REPLACE_KEY = "replace"; - public static final String SETTINGS_LATEST_CHANGES_KEY = "settings_latest_changes"; - public static final String SETTINGS_VERSION_KEY = "settings_version"; - - private static final Log LOG = PlatformUtil.getLog(SettingsHelper.class); - private static final int BUFFER = 1024; - - private OsmandApplication app; - - private ImportAsyncTask importTask; - private Map exportAsyncTasks = new HashMap<>(); - - public interface SettingsImportListener { - void onSettingsImportFinished(boolean succeed, @NonNull List items); - } - - public interface SettingsCollectListener { - void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items); - } - - public interface CheckDuplicatesListener { - void onDuplicatesChecked(@NonNull List duplicates, List items); - } - - public interface SettingsExportListener { - void onSettingsExportFinished(@NonNull File file, boolean succeed); - } - - public SettingsHelper(@NonNull OsmandApplication app) { - this.app = app; - } - - public enum SettingsItemType { - GLOBAL, - PROFILE, - PLUGIN, - DATA, - FILE, - RESOURCES, - QUICK_ACTIONS, - POI_UI_FILTERS, - MAP_SOURCES, - AVOID_ROADS, - SUGGESTED_DOWNLOADS, - DOWNLOADS - } - - public abstract static class SettingsItem { - - protected OsmandApplication app; - - protected String pluginId; - protected String fileName; - - boolean shouldReplace = false; - - protected List warnings; - - SettingsItem(@NonNull OsmandApplication app) { - this.app = app; - init(); - } - - SettingsItem(@NonNull OsmandApplication app, @Nullable SettingsItem baseItem) { - this.app = app; - if (baseItem != null) { - this.pluginId = baseItem.pluginId; - this.fileName = baseItem.fileName; - } - init(); - } - - SettingsItem(OsmandApplication app, @NonNull JSONObject json) throws JSONException { - this.app = app; - init(); - readFromJson(json); - } - - protected void init() { - warnings = new ArrayList<>(); - } - - public List getWarnings() { - return warnings; - } - - @NonNull - public abstract SettingsItemType getType(); - - @NonNull - public abstract String getName(); - - @NonNull - public abstract String getPublicName(@NonNull Context ctx); - - @NonNull - public String getDefaultFileName() { - return getName() + getDefaultFileExtension(); - } - - @NonNull - public String getDefaultFileExtension() { - return ".json"; - } - - public String getPluginId() { - return pluginId; - } - - @Nullable - public String getFileName() { - return fileName; - } - - public boolean applyFileName(@NonNull String fileName) { - String n = getFileName(); - return n != null && n.endsWith(fileName); - } - - public boolean shouldReadOnCollecting() { - return false; - } - - public void setShouldReplace(boolean shouldReplace) { - this.shouldReplace = shouldReplace; - } - - static SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { - String type = json.has("type") ? json.getString("type") : null; - if (type == null) { - throw new IllegalArgumentException("No type field"); - } - if (type.equals("QUICK_ACTION")) { - type = "QUICK_ACTIONS"; - } - return SettingsItemType.valueOf(type); - } - - public boolean exists() { - return false; - } - - public void apply() { - // non implemented - } - - void readFromJson(@NonNull JSONObject json) throws JSONException { - pluginId = json.has("pluginId") ? json.getString("pluginId") : null; - if (json.has("name")) { - fileName = json.getString("name") + getDefaultFileExtension(); - } - if (json.has("file")) { - fileName = json.getString("file"); - } - readItemsFromJson(json); - } - - void writeToJson(@NonNull JSONObject json) throws JSONException { - json.put("type", getType().name()); - String pluginId = getPluginId(); - if (!Algorithms.isEmpty(pluginId)) { - json.put("pluginId", pluginId); - } - if (getWriter() != null) { - String fileName = getFileName(); - if (Algorithms.isEmpty(fileName)) { - fileName = getDefaultFileName(); - } - json.put("file", fileName); - } - writeItemsToJson(json); - } - - String toJson() throws JSONException { - JSONObject json = new JSONObject(); - writeToJson(json); - return json.toString(); - } - - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - // override - } - - void writeItemsToJson(@NonNull JSONObject json) { - // override - } - - @Nullable - abstract SettingsItemReader getReader(); - - @Nullable - abstract SettingsItemWriter getWriter(); - - @NonNull - SettingsItemReader getJsonReader() { - return new SettingsItemReader(this) { - @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 json = buf.toString(); - if (json.length() == 0) { - throw new IllegalArgumentException("Json body is empty"); - } - try { - readItemsFromJson(new JSONObject(json)); - } catch (JSONException e) { - throw new IllegalArgumentException("Json parsing error", e); - } - } - }; - } - - @NonNull - SettingsItemWriter getJsonWriter() { - return new SettingsItemWriter(this) { - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - JSONObject json = new JSONObject(); - writeItemsToJson(json); - if (json.length() > 0) { - try { - String s = json.toString(2); - outputStream.write(s.getBytes("UTF-8")); - } catch (JSONException e) { - LOG.error("Failed to write json to stream", e); - } - return true; - } - return false; - } - }; - } - - @Override - public int hashCode() { - return (getType().name() + getName()).hashCode(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof SettingsItem)) { - return false; - } - - SettingsItem item = (SettingsItem) other; - return item.getType() == getType() - && item.getName().equals(getName()) - && Algorithms.stringsEqual(item.getFileName(), getFileName()); - } - } - - public static class PluginSettingsItem extends SettingsItem { - - private CustomOsmandPlugin plugin; - private List pluginDependentItems; - - PluginSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - pluginDependentItems = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.PLUGIN; - } - - @NonNull - @Override - public String getName() { - return plugin.getId(); - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return plugin.getName(); - } - - @NonNull - @Override - public String getDefaultFileName() { - return getName(); - } - - public CustomOsmandPlugin getPlugin() { - return plugin; - } - - public List getPluginDependentItems() { - return pluginDependentItems; - } - - @Override - public boolean exists() { - return OsmandPlugin.getPlugin(getPluginId()) != null; - } - - @Override - public void apply() { - if (shouldReplace || !exists()) { - for (SettingsHelper.SettingsItem item : pluginDependentItems) { - if (item instanceof SettingsHelper.FileSettingsItem) { - FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { - plugin.addRenderer(fileItem.getName()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { - plugin.addRouter(fileItem.getName()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.OTHER) { - plugin.setResourceDirName(item.getFileName()); - } - } else if (item instanceof SuggestedDownloadsItem) { - plugin.updateSuggestedDownloads(((SuggestedDownloadsItem) item).getItems()); - } else if (item instanceof DownloadsItem) { - plugin.updateDownloadItems(((DownloadsItem) item).getItems()); - } - } - OsmandPlugin.addCustomPlugin(app, plugin); - } - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - plugin = new CustomOsmandPlugin(app, json); - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - plugin.writeAdditionalDataToJson(json); - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class SuggestedDownloadsItem extends SettingsItem { - - private List items; - - SuggestedDownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.SUGGESTED_DOWNLOADS; - - } - - @NonNull - @Override - public String getName() { - return "suggested_downloads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "suggested_downloads"; - } - - public List getItems() { - return items; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - String scopeId = object.optString("scope-id"); - String searchType = object.optString("search-type"); - int limit = object.optInt("limit", -1); - - List names = new ArrayList<>(); - if (object.has("names")) { - JSONArray namesArray = object.getJSONArray("names"); - for (int j = 0; j < namesArray.length(); j++) { - names.add(namesArray.getString(j)); - } - } - SuggestedDownloadItem suggestedDownload = new SuggestedDownloadItem(scopeId, searchType, names, limit); - items.add(suggestedDownload); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (SuggestedDownloadItem downloadItem : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("scope-id", downloadItem.getScopeId()); - if (downloadItem.getLimit() != -1) { - jsonObject.put("limit", downloadItem.getLimit()); - } - if (!Algorithms.isEmpty(downloadItem.getSearchType())) { - jsonObject.put("search-type", downloadItem.getSearchType()); - } - if (!Algorithms.isEmpty(downloadItem.getNames())) { - JSONArray namesArray = new JSONArray(); - for (String downloadName : downloadItem.getNames()) { - namesArray.put(downloadName); - } - jsonObject.put("names", namesArray); - } - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class DownloadsItem extends SettingsItem { - - private List items; - - DownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.DOWNLOADS; - - } - - @NonNull - @Override - public String getName() { - return "downloads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "downloads"; - } - - public List getItems() { - return items; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - items.addAll(CustomOsmandPlugin.collectRegionsFromJson(app, jsonArray)); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (WorldRegion region : items) { - if (region instanceof CustomRegion) { - JSONObject regionJson = ((CustomRegion) region).toJson(); - jsonArray.put(regionJson); - } - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return null; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public abstract static class CollectionSettingsItem extends SettingsItem { - - protected List items; - protected List appliedItems; - protected List duplicateItems; - protected List existingItems; - - @Override - protected void init() { - super.init(); - items = new ArrayList<>(); - appliedItems = new ArrayList<>(); - duplicateItems = new ArrayList<>(); - } - - CollectionSettingsItem(@NonNull OsmandApplication app, @Nullable CollectionSettingsItem baseItem, @NonNull List items) { - super(app, baseItem); - this.items = items; - } - - CollectionSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @NonNull - public List getItems() { - return items; - } - - @NonNull - public List getAppliedItems() { - return appliedItems; - } - - @NonNull - public List getDuplicateItems() { - return duplicateItems; - } - - @NonNull - public List processDuplicateItems() { - if (!items.isEmpty()) { - for (T item : items) { - if (isDuplicate(item)) { - duplicateItems.add(item); - } - } - } - return duplicateItems; - } - - public List getNewItems() { - List res = new ArrayList<>(items); - res.removeAll(duplicateItems); - return res; - } - - public abstract boolean isDuplicate(@NonNull T item); - - @NonNull - public abstract T renameItem(@NonNull T item); - } - - public abstract static class SettingsItemReader { - - private T item; - - public SettingsItemReader(@NonNull T item) { - this.item = item; - } - - public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException; - } - - public abstract static class SettingsItemWriter { - - private T item; - - public SettingsItemWriter(T item) { - this.item = item; - } - - public T getItem() { - return item; - } - - public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException; - } - - public abstract static class OsmandSettingsItem extends SettingsItem { - - private OsmandSettings settings; - - protected OsmandSettingsItem(@NonNull OsmandSettings settings) { - super(settings.getContext()); - this.settings = settings; - } - - protected OsmandSettingsItem(@NonNull OsmandSettings settings, @Nullable OsmandSettingsItem baseItem) { - super(settings.getContext(), baseItem); - this.settings = settings; - } - - protected OsmandSettingsItem(@NonNull SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { - super(settings.getContext(), json); - this.settings = settings; - } - - public OsmandSettings getSettings() { - return settings; - } - } - - public abstract static class OsmandSettingsItemReader extends SettingsItemReader { - - private OsmandSettings settings; - - public OsmandSettingsItemReader(@NonNull T item, @NonNull OsmandSettings settings) { - super(item); - this.settings = settings; - } - - protected abstract 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 { - json = new JSONObject(jsonStr); - } catch (JSONException e) { - throw new IllegalArgumentException("Json parse error", e); - } - readPreferencesFromJson(json); - } - - void readPreferencesFromJson(final JSONObject json) { - settings.getContext().runInUIThread(new Runnable() { - @Override - public void run() { - Map> prefs = settings.getRegisteredPreferences(); - Iterator iter = json.keys(); - while (iter.hasNext()) { - String key = iter.next(); - OsmandPreference p = prefs.get(key); - if (p != null) { - try { - readPreferenceFromJson(p, json); - } catch (Exception e) { - LOG.error("Failed to read preference: " + key, e); - } - } else { - LOG.warn("No preference while importing settings: " + key); - } - } - } - }); - } - } - - public abstract static class OsmandSettingsItemWriter extends SettingsItemWriter { - - private OsmandSettings settings; - - public OsmandSettingsItemWriter(@NonNull T item, @NonNull OsmandSettings settings) { - super(item); - this.settings = settings; - } - - protected abstract void writePreferenceToJson(@NonNull OsmandPreference preference, - @NonNull JSONObject json) throws JSONException; - - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - JSONObject json = new JSONObject(); - Map> prefs = settings.getRegisteredPreferences(); - for (OsmandPreference pref : prefs.values()) { - try { - writePreferenceToJson(pref, json); - } catch (JSONException e) { - LOG.error("Failed to write preference: " + pref.getId(), e); - } - } - if (json.length() > 0) { - try { - String s = json.toString(2); - outputStream.write(s.getBytes("UTF-8")); - } catch (JSONException e) { - LOG.error("Failed to write json to stream", e); - } - return true; - } - return false; - } - } - - public static class GlobalSettingsItem extends OsmandSettingsItem { - - public GlobalSettingsItem(@NonNull OsmandSettings settings) { - super(settings); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.GLOBAL; - } - - @NonNull - @Override - public String getName() { - return "general_settings"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return ctx.getString(R.string.general_settings_2); - } - - @Override - public boolean exists() { - return true; - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new OsmandSettingsItemReader(this, getSettings()) { - @Override - protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - preference.readFromJson(json, null); - } - }; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return new OsmandSettingsItemWriter(this, getSettings()) { - @Override - protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - preference.writeToJson(json, null); - } - }; - } - } - - public static class ProfileSettingsItem extends OsmandSettingsItem { - - private ApplicationMode appMode; - private ApplicationModeBuilder builder; - private ApplicationModeBean modeBean; - - private JSONObject additionalPrefsJson; - private Set appModeBeanPrefsIds; - - public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull ApplicationMode appMode) { - super(app.getSettings()); - this.appMode = appMode; - } - - public ProfileSettingsItem(@NonNull OsmandApplication app, @Nullable ProfileSettingsItem baseItem, @NonNull ApplicationModeBean modeBean) { - super(app.getSettings(), baseItem); - this.modeBean = modeBean; - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = builder.getApplicationMode(); - } - - public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(SettingsItemType.PROFILE, app.getSettings(), json); - } - - @Override - protected void init() { - super.init(); - appModeBeanPrefsIds = new HashSet<>(Arrays.asList(getAppModeBeanPrefsIds())); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.PROFILE; - } - - public ApplicationMode getAppMode() { - return appMode; - } - - public ApplicationModeBean getModeBean() { - return modeBean; - } - - @NonNull - @Override - public String getName() { - return appMode.getStringKey(); - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - if (appMode.isCustomProfile()) { - return modeBean.userProfileName; - } else if (appMode.getNameKeyResource() != -1) { - return ctx.getString(appMode.getNameKeyResource()); - } else { - return getName(); - } - } - - @NonNull - @Override - public String getDefaultFileName() { - return "profile_" + getName() + getDefaultFileExtension(); - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String appModeJson = json.getString("appMode"); - modeBean = ApplicationMode.fromJson(appModeJson); - builder = ApplicationMode.fromModeBean(app, modeBean); - ApplicationMode appMode = builder.getApplicationMode(); - if (!appMode.isCustomProfile()) { - appMode = ApplicationMode.valueOfStringKey(appMode.getStringKey(), appMode); - } - this.appMode = appMode; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - additionalPrefsJson = json.optJSONObject("prefs"); - } - - @Override - public boolean exists() { - return builder != null && ApplicationMode.valueOfStringKey(getName(), null) != null; - } - - private void renameProfile() { - List values = ApplicationMode.allPossibleValues(); - if (Algorithms.isEmpty(modeBean.userProfileName)) { - ApplicationMode appMode = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); - if (appMode != null) { - modeBean.userProfileName = app.getString(appMode.getNameKeyResource()); - } - } - int number = 0; - while (true) { - number++; - String key = modeBean.stringKey + "_" + number; - String name = modeBean.userProfileName + '_' + number; - if (ApplicationMode.valueOfStringKey(key, null) == null && isNameUnique(values, name)) { - modeBean.userProfileName = name; - modeBean.stringKey = key; - break; - } - } - } - - private boolean isNameUnique(List values, String name) { - for (ApplicationMode mode : values) { - if (mode.getUserProfileName().equals(name)) { - return false; - } - } - return true; - } - - @Override - public void apply() { - if (!appMode.isCustomProfile() && !shouldReplace) { - ApplicationMode parent = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); - renameProfile(); - ApplicationMode.ApplicationModeBuilder builder = ApplicationMode - .createCustomMode(parent, modeBean.stringKey, app) - .setIconResName(modeBean.iconName) - .setUserProfileName(modeBean.userProfileName) - .setRoutingProfile(modeBean.routingProfile) - .setRouteService(modeBean.routeService) - .setIconColor(modeBean.iconColor) - .setLocationIcon(modeBean.locIcon) - .setNavigationIcon(modeBean.navIcon); - app.getSettings().copyPreferencesFromProfile(parent, builder.getApplicationMode()); - appMode = ApplicationMode.saveProfile(builder, app); - } else if (!shouldReplace && exists()) { - renameProfile(); - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = ApplicationMode.saveProfile(builder, app); - } else { - builder = ApplicationMode.fromModeBean(app, modeBean); - appMode = ApplicationMode.saveProfile(builder, app); - } - ApplicationMode.changeProfileAvailability(appMode, true, app); - } - - public void applyAdditionalPrefs() { - if (additionalPrefsJson != null) { - updatePluginResPrefs(); - - SettingsItemReader reader = getReader(); - if (reader instanceof OsmandSettingsItemReader) { - ((OsmandSettingsItemReader) reader).readPreferencesFromJson(additionalPrefsJson); - } - } - } - - private void updatePluginResPrefs() { - String pluginId = getPluginId(); - if (Algorithms.isEmpty(pluginId)) { - return; - } - OsmandPlugin plugin = OsmandPlugin.getPlugin(pluginId); - if (plugin instanceof CustomOsmandPlugin) { - CustomOsmandPlugin customPlugin = (CustomOsmandPlugin) plugin; - String resDirPath = IndexConstants.PLUGINS_DIR + pluginId + "/" + customPlugin.getResourceDirName(); - - for (Iterator it = additionalPrefsJson.keys(); it.hasNext(); ) { - try { - String prefId = it.next(); - Object value = additionalPrefsJson.get(prefId); - if (value instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) value; - for (Iterator iterator = jsonObject.keys(); iterator.hasNext(); ) { - String key = iterator.next(); - Object val = jsonObject.get(key); - if (val instanceof String) { - val = checkPluginResPath((String) val, resDirPath); - } - jsonObject.put(key, val); - } - } else if (value instanceof String) { - value = checkPluginResPath((String) value, resDirPath); - additionalPrefsJson.put(prefId, value); - } - } catch (JSONException e) { - LOG.error(e); - } - } - } - } - - private String checkPluginResPath(String path, String resDirPath) { - if (path.startsWith("@")) { - return resDirPath + "/" + path.substring(1); - } - return path; - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - json.put("appMode", new JSONObject(appMode.toJson())); - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new OsmandSettingsItemReader(this, getSettings()) { - @Override - protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - if (!appModeBeanPrefsIds.contains(preference.getId())) { - preference.readFromJson(json, appMode); - } - } - - @Override - void readPreferencesFromJson(final JSONObject json) { - getSettings().getContext().runInUIThread(new Runnable() { - @Override - public void run() { - OsmandSettings settings = getSettings(); - Map> prefs = settings.getRegisteredPreferences(); - Iterator iter = json.keys(); - while (iter.hasNext()) { - String key = iter.next(); - OsmandPreference p = prefs.get(key); - if (p == null) { - if (OsmandSettings.isRoutingPreference(key)) { - p = settings.registerStringPreference(key, ""); - } - } - if (p != null) { - try { - readPreferenceFromJson(p, json); - if (OsmandSettings.isRoutingPreference(p.getId())) { - if (p.getId().endsWith(GeneralRouter.USE_SHORTEST_WAY)) { - settings.FAST_ROUTE_MODE.setModeValue(appMode, - !settings.getCustomRoutingBooleanProperty(GeneralRouter.USE_SHORTEST_WAY, false).getModeValue(appMode)); - } - } - } catch (Exception e) { - LOG.error("Failed to read preference: " + key, e); - } - } else { - LOG.warn("No preference while importing settings: " + key); - } - } - } - }); - } - }; - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return new OsmandSettingsItemWriter(this, getSettings()) { - @Override - protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { - if (!appModeBeanPrefsIds.contains(preference.getId())) { - preference.writeToJson(json, appMode); - } - } - }; - } - - private String[] getAppModeBeanPrefsIds() { - OsmandSettings settings = app.getSettings(); - return new String[] { - settings.ICON_COLOR.getId(), - settings.ICON_RES_NAME.getId(), - settings.PARENT_APP_MODE.getId(), - settings.ROUTING_PROFILE.getId(), - settings.ROUTE_SERVICE.getId(), - settings.USER_PROFILE_NAME.getId(), - settings.LOCATION_ICON.getId(), - settings.NAVIGATION_ICON.getId(), - settings.APP_MODE_ORDER.getId() - }; - } - } - - public abstract static class StreamSettingsItemReader extends SettingsItemReader { - - public StreamSettingsItemReader(@NonNull StreamSettingsItem item) { - super(item); - } - } - - public static class StreamSettingsItemWriter extends SettingsItemWriter { - - public StreamSettingsItemWriter(StreamSettingsItem item) { - super(item); - } - - @Override - public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { - boolean hasData = false; - InputStream is = getItem().inputStream; - if (is != null) { - byte[] data = new byte[BUFFER]; - int count; - while ((count = is.read(data, 0, BUFFER)) != -1) { - outputStream.write(data, 0, count); - if (!hasData) { - hasData = true; - } - } - Algorithms.closeStream(is); - } - return hasData; - } - } - - public abstract static class StreamSettingsItem extends SettingsItem { - - @Nullable - private InputStream inputStream; - protected String name; - - public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { - super(app); - this.name = name; - this.fileName = name; - } - - StreamSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull InputStream inputStream, @NonNull String name) { - super(app); - this.inputStream = inputStream; - this.name = name; - this.fileName = name; - } - - @Nullable - public InputStream getInputStream() { - return inputStream; - } - - protected void setInputStream(@Nullable InputStream inputStream) { - this.inputStream = inputStream; - } - - @NonNull - @Override - public String getName() { - return name; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return getName(); - } - - @NonNull - @Override - public String getDefaultFileExtension() { - return ""; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - name = json.has("name") ? json.getString("name") : null; - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - return new StreamSettingsItemWriter(this); - } - } - - public static class DataSettingsItem extends StreamSettingsItem { - - @Nullable - private byte[] data; - - public DataSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { - super(app, name); - } - - DataSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - public DataSettingsItem(@NonNull OsmandApplication app, @NonNull byte[] data, @NonNull String name) { - super(app, name); - this.data = data; - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.DATA; - } - - @NonNull - @Override - public String getDefaultFileExtension() { - return ".dat"; - } - - @Nullable - public byte[] getData() { - return data; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName)) { - name = Algorithms.getFileNameWithoutExtension(new File(fileName)); - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new StreamSettingsItemReader(this) { - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[BUFFER]; - while ((nRead = inputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - buffer.flush(); - DataSettingsItem.this.data = buffer.toByteArray(); - } - }; - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - setInputStream(new ByteArrayInputStream(data)); - return super.getWriter(); - } - } - - public static class FileSettingsItem extends StreamSettingsItem { - - public enum FileSubtype { - UNKNOWN("", null), - OTHER("other", ""), - ROUTING_CONFIG("routing_config", IndexConstants.ROUTING_PROFILES_DIR), - RENDERING_STYLE("rendering_style", IndexConstants.RENDERERS_DIR), - OBF_MAP("obf_map", IndexConstants.MAPS_PATH), - TILES_MAP("tiles_map", IndexConstants.TILES_INDEX_DIR), - GPX("gpx", IndexConstants.GPX_INDEX_DIR), - VOICE("voice", IndexConstants.VOICE_INDEX_DIR), - TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR); - - private String subtypeName; - private String subtypeFolder; - - FileSubtype(String subtypeName, String subtypeFolder) { - this.subtypeName = subtypeName; - this.subtypeFolder = subtypeFolder; - } - - public String getSubtypeName() { - return subtypeName; - } - - public String getSubtypeFolder() { - return subtypeFolder; - } - - public static FileSubtype getSubtypeByName(@NonNull String name) { - for (FileSubtype subtype : FileSubtype.values()) { - if (name.equals(subtype.subtypeName)) { - return subtype; - } - } - return null; - } - - public static FileSubtype getSubtypeByFileName(@NonNull String fileName) { - String name = fileName; - if (fileName.startsWith(File.separator)) { - name = fileName.substring(1); - } - for (FileSubtype subtype : FileSubtype.values()) { - switch (subtype) { - case UNKNOWN: - case OTHER: - break; - case OBF_MAP: - if (name.endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { - return subtype; - } - break; - default: - if (name.startsWith(subtype.subtypeFolder)) { - return subtype; - } - break; - } - } - return UNKNOWN; - } - - @NonNull - @Override - public String toString() { - return subtypeName; - } - } - - protected File file; - private File appPath; - protected FileSubtype subtype; - - public FileSettingsItem(@NonNull OsmandApplication app, @NonNull File file) throws IllegalArgumentException { - super(app, file.getPath().replace(app.getAppPath(null).getPath(), "")); - this.file = file; - this.appPath = app.getAppPath(null); - String fileName = getFileName(); - if (fileName != null) { - this.subtype = FileSubtype.getSubtypeByFileName(fileName); - } - if (subtype == FileSubtype.UNKNOWN || subtype == null) { - throw new IllegalArgumentException("Unknown file subtype: " + fileName); - } - } - - FileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - this.appPath = app.getAppPath(null); - if (subtype == FileSubtype.OTHER) { - this.file = new File(appPath, name); - } else if (subtype == FileSubtype.UNKNOWN || subtype == null) { - throw new IllegalArgumentException("Unknown file subtype: " + getFileName()); - } else { - this.file = new File(app.getAppPath(subtype.subtypeFolder), name); - } - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.FILE; - } - - public File getPluginPath() { - String pluginId = getPluginId(); - if (!Algorithms.isEmpty(pluginId)) { - return new File(appPath, IndexConstants.PLUGINS_DIR + pluginId); - } - return appPath; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - super.readFromJson(json); - String fileName = getFileName(); - if (subtype == null) { - String subtypeStr = json.has("subtype") ? json.getString("subtype") : null; - if (!Algorithms.isEmpty(subtypeStr)) { - subtype = FileSubtype.getSubtypeByName(subtypeStr); - } else if (!Algorithms.isEmpty(fileName)) { - subtype = FileSubtype.getSubtypeByFileName(fileName); - } else { - subtype = FileSubtype.UNKNOWN; - } - } - if (!Algorithms.isEmpty(fileName)) { - if (subtype == FileSubtype.OTHER) { - name = fileName; - } else if (subtype != null && subtype != FileSubtype.UNKNOWN) { - name = Algorithms.getFileWithoutDirs(fileName); - } - } - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - if (subtype != null) { - json.put("subtype", subtype.getSubtypeName()); - } - } - - @NonNull - public File getFile() { - return file; - } - - @NonNull - public FileSubtype getSubtype() { - return subtype; - } - - @Override - public boolean exists() { - return file.exists(); - } - - private File renameFile(File file) { - int number = 0; - String path = file.getAbsolutePath(); - while (true) { - number++; - String copyName = path.replaceAll(file.getName(), file.getName().replaceFirst("[.]", "_" + number + ".")); - File copyFile = new File(copyName); - if (!copyFile.exists()) { - return copyFile; - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return new StreamSettingsItemReader(this) { - @Override - public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { - OutputStream output; - File dest = FileSettingsItem.this.file; - if (dest.exists() && !shouldReplace) { - dest = renameFile(dest); - } - if (dest.getParentFile() != null && !dest.getParentFile().exists()) { - dest.getParentFile().mkdirs(); - } - output = new FileOutputStream(dest); - byte[] buffer = new byte[BUFFER]; - int count; - try { - while ((count = inputStream.read(buffer)) != -1) { - output.write(buffer, 0, count); - } - output.flush(); - } finally { - Algorithms.closeStream(output); - } - } - }; - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - try { - setInputStream(new FileInputStream(file)); - } catch (FileNotFoundException e) { - warnings.add(app.getString(R.string.settings_item_read_error, file.getName())); - LOG.error("Failed to set input stream from file: " + file.getName(), e); - } - return super.getWriter(); - } - } - - public static class ResourcesSettingsItem extends FileSettingsItem { - - ResourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - shouldReplace = true; - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName) && !fileName.endsWith(File.separator)) { - this.fileName = fileName + File.separator; - } - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.RESOURCES; - } - - @Override - void readFromJson(@NonNull JSONObject json) throws JSONException { - subtype = FileSubtype.OTHER; - super.readFromJson(json); - } - - @Override - void writeToJson(@NonNull JSONObject json) throws JSONException { - super.writeToJson(json); - String fileName = getFileName(); - if (!Algorithms.isEmpty(fileName)) { - if (fileName.endsWith(File.separator)) { - fileName = fileName.substring(0, fileName.length() - 1); - } - json.put("file", fileName); - } - } - - @Override - public boolean applyFileName(@NonNull String fileName) { - if (fileName.endsWith(File.separator)) { - return false; - } - String itemFileName = getFileName(); - if (itemFileName != null && itemFileName.endsWith(File.separator)) { - if (fileName.startsWith(itemFileName)) { - this.file = new File(getPluginPath(), fileName); - return true; - } else { - return false; - } - } else { - return super.applyFileName(fileName); - } - } - - @Nullable - @Override - public SettingsItemWriter getWriter() { - return null; - } - } - - public static class QuickActionsSettingsItem extends CollectionSettingsItem { - - private QuickActionRegistry actionRegistry; - - public QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public QuickActionsSettingsItem(@NonNull OsmandApplication app, @Nullable QuickActionsSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - actionRegistry = app.getQuickActionRegistry(); - existingItems = actionRegistry.getQuickActions(); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.QUICK_ACTIONS; - } - - @Override - public boolean isDuplicate(@NonNull QuickAction item) { - return !actionRegistry.isNameUnique(item, app); - } - - @NonNull - @Override - public QuickAction renameItem(@NonNull QuickAction item) { - return actionRegistry.generateUniqueName(item, app); - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - List newActions = new ArrayList<>(existingItems); - if (!duplicateItems.isEmpty()) { - if (shouldReplace) { - for (QuickAction duplicateItem : duplicateItems) { - for (QuickAction savedAction : existingItems) { - if (duplicateItem.getName(app).equals(savedAction.getName(app))) { - newActions.remove(savedAction); - } - } - } - } else { - for (QuickAction duplicateItem : duplicateItems) { - renameItem(duplicateItem); - } - } - appliedItems.addAll(duplicateItems); - } - newActions.addAll(appliedItems); - actionRegistry.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"; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - QuickActionRegistry quickActionRegistry = app.getQuickActionRegistry(); - JSONArray itemsJson = json.getJSONArray("items"); - for (int i = 0; i < itemsJson.length(); i++) { - JSONObject object = itemsJson.getJSONObject(i); - String name = object.getString("name"); - QuickAction quickAction = null; - if (object.has("actionType")) { - quickAction = quickActionRegistry.newActionByStringType(object.getString("actionType")); - } else if (object.has("type")) { - quickAction = quickActionRegistry.newActionByType(object.getInt("type")); - } - if (quickAction != null) { - String paramsString = object.getString("params"); - HashMap params = gson.fromJson(paramsString, type); - - if (!name.isEmpty()) { - quickAction.setName(name); - } - quickAction.setParams(params); - items.add(quickAction); - } else { - warnings.add(app.getString(R.string.settings_item_read_error, name)); - } - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); - if (!items.isEmpty()) { - try { - for (QuickAction action : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", action.hasCustomName(app) - ? action.getName(app) : ""); - jsonObject.put("actionType", action.getActionType().getStringId()); - jsonObject.put("params", gson.toJson(action.getParams(), type)); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class PoiUiFiltersSettingsItem extends CollectionSettingsItem { - - public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @Nullable PoiUiFiltersSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - existingItems = app.getPoiFilters().getUserDefinedPoiFilters(false); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.POI_UI_FILTERS; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - - for (PoiUIFilter duplicate : duplicateItems) { - appliedItems.add(shouldReplace ? duplicate : renameItem(duplicate)); - } - for (PoiUIFilter filter : appliedItems) { - app.getPoiFilters().createPoiFilter(filter, false); - } - app.getSearchUICore().refreshCustomPoiFilters(); - } - } - - @Override - public boolean isDuplicate(@NonNull PoiUIFilter item) { - String savedName = item.getName(); - for (PoiUIFilter filter : existingItems) { - if (filter.getName().equals(savedName)) { - return true; - } - } - return false; - } - - @NonNull - @Override - public PoiUIFilter renameItem(@NonNull PoiUIFilter item) { - int number = 0; - while (true) { - number++; - PoiUIFilter renamedItem = new PoiUIFilter(item, - item.getName() + "_" + number, - item.getFilterId() + "_" + number); - if (!isDuplicate(renamedItem)) { - return renamedItem; - } - } - } - - @NonNull - @Override - public String getName() { - return "poi_ui_filters"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "poi_ui_filters"; - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - Gson gson = new Gson(); - Type type = new TypeToken>>() { - }.getType(); - MapPoiTypes poiTypes = app.getPoiTypes(); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.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); - items.add(filter); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - Gson gson = new Gson(); - Type type = new TypeToken>>() { - }.getType(); - if (!items.isEmpty()) { - try { - for (PoiUIFilter filter : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", filter.getName()); - jsonObject.put("filterId", filter.getFilterId()); - jsonObject.put("acceptedTypes", gson.toJson(filter.getAcceptedTypes(), type)); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class MapSourcesSettingsItem extends CollectionSettingsItem { - - private List existingItemsNames; - - public MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public MapSourcesSettingsItem(@NonNull OsmandApplication app, @Nullable MapSourcesSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - existingItemsNames = new ArrayList<>(app.getSettings().getTileSourceEntries().values()); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.MAP_SOURCES; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - if (shouldReplace) { - for (ITileSource tileSource : duplicateItems) { - if (tileSource instanceof SQLiteTileSource) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName() + IndexConstants.SQLITE_EXT); - if (f != null && f.exists() && Algorithms.removeAllFiles(f)) { - appliedItems.add(tileSource); - } - } else if (tileSource instanceof TileSourceManager.TileSourceTemplate) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName()); - if (f != null && f.exists() && f.isDirectory() && Algorithms.removeAllFiles(f)) { - appliedItems.add(tileSource); - } - } - } - } else { - for (ITileSource tileSource : duplicateItems) { - appliedItems.add(renameItem(tileSource)); - } - } - for (ITileSource tileSource : appliedItems) { - if (tileSource instanceof TileSourceManager.TileSourceTemplate) { - app.getSettings().installTileSource((TileSourceManager.TileSourceTemplate) tileSource); - } else if (tileSource instanceof SQLiteTileSource) { - ((SQLiteTileSource) tileSource).createDataBase(); - } - } - } - } - - @NonNull - @Override - public ITileSource renameItem(@NonNull ITileSource item) { - int number = 0; - while (true) { - number++; - if (item instanceof SQLiteTileSource) { - SQLiteTileSource oldItem = (SQLiteTileSource) item; - SQLiteTileSource renamedItem = new SQLiteTileSource( - oldItem, - oldItem.getName() + "_" + number, - app); - if (!isDuplicate(renamedItem)) { - return renamedItem; - } - } else if (item instanceof TileSourceManager.TileSourceTemplate) { - TileSourceManager.TileSourceTemplate oldItem = (TileSourceManager.TileSourceTemplate) item; - oldItem.setName(oldItem.getName() + "_" + number); - if (!isDuplicate(oldItem)) { - return oldItem; - } - } - } - } - - @Override - public boolean isDuplicate(@NonNull ITileSource item) { - for (String name : existingItemsNames) { - if (name.equals(item.getName())) { - return true; - } - } - return false; - } - - @NonNull - @Override - public String getName() { - return "map_sources"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "map_sources"; - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.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"); - String userAgent = object.optString("userAgent"); - boolean timeSupported = object.optBoolean("timesupported", false); - long expire = object.optLong("expire", -1); - 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"); - - if (expire > 0 && expire < 3600000) { - expire = expire * 60 * 1000L; - } - - ITileSource template; - if (!sql) { - TileSourceTemplate tileSourceTemplate = new TileSourceTemplate(name, url, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); - tileSourceTemplate.setRule(rule); - tileSourceTemplate.setRandoms(randoms); - tileSourceTemplate.setReferer(referer); - tileSourceTemplate.setUserAgent(userAgent); - tileSourceTemplate.setEllipticYTile(ellipsoid); - tileSourceTemplate.setInvertedYTile(invertedY); - tileSourceTemplate.setExpirationTimeMillis(timeSupported ? expire : -1); - - template = tileSourceTemplate; - } else { - template = new SQLiteTileSource(app, name, minZoom, maxZoom, url, randoms, ellipsoid, invertedY, referer, userAgent, timeSupported, expire, inversiveZoom, rule); - } - items.add(template); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (ITileSource template : items) { - 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("userAgent", template.getUserAgent()); - jsonObject.put("timesupported", template.isTimeSupported()); - jsonObject.put("expire", template.getExpirationTimeMinutes()); - 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()); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - public static class AvoidRoadsSettingsItem extends CollectionSettingsItem { - - private OsmandSettings settings; - private AvoidSpecificRoads specificRoads; - - public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { - super(app, null, items); - } - - public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @Nullable AvoidRoadsSettingsItem baseItem, @NonNull List items) { - super(app, baseItem, items); - } - - AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { - super(app, json); - } - - @Override - protected void init() { - super.init(); - settings = app.getSettings(); - specificRoads = app.getAvoidSpecificRoads(); - existingItems = new ArrayList<>(specificRoads.getImpassableRoads().values()); - } - - @NonNull - @Override - public SettingsItemType getType() { - return SettingsItemType.AVOID_ROADS; - } - - @NonNull - @Override - public String getName() { - return "avoid_roads"; - } - - @NonNull - @Override - public String getPublicName(@NonNull Context ctx) { - return "avoid_roads"; - } - - @Override - public void apply() { - List newItems = getNewItems(); - if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { - appliedItems = new ArrayList<>(newItems); - for (AvoidRoadInfo duplicate : duplicateItems) { - if (shouldReplace) { - LatLon latLon = new LatLon(duplicate.latitude, duplicate.longitude); - if (settings.removeImpassableRoad(latLon)) { - settings.addImpassableRoad(duplicate); - } - } else { - settings.addImpassableRoad(renameItem(duplicate)); - } - } - for (AvoidRoadInfo avoidRoad : appliedItems) { - settings.addImpassableRoad(avoidRoad); - } - specificRoads.loadImpassableRoads(); - specificRoads.initRouteObjects(true); - } - } - - @Override - public boolean isDuplicate(@NonNull AvoidRoadInfo item) { - return existingItems.contains(item); - } - - @Override - public boolean shouldReadOnCollecting() { - return true; - } - - @NonNull - @Override - public AvoidRoadInfo renameItem(@NonNull AvoidRoadInfo item) { - int number = 0; - while (true) { - number++; - AvoidRoadInfo renamedItem = new AvoidRoadInfo(); - renamedItem.name = item.name + "_" + number; - if (!isDuplicate(renamedItem)) { - renamedItem.id = item.id; - renamedItem.latitude = item.latitude; - renamedItem.longitude = item.longitude; - renamedItem.appModeKey = item.appModeKey; - return renamedItem; - } - } - } - - @Override - void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { - try { - if (!json.has("items")) { - return; - } - JSONArray jsonArray = json.getJSONArray("items"); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject object = jsonArray.getJSONObject(i); - double latitude = object.optDouble("latitude"); - double longitude = object.optDouble("longitude"); - String name = object.optString("name"); - String appModeKey = object.optString("appModeKey"); - AvoidRoadInfo roadInfo = new AvoidRoadInfo(); - roadInfo.id = 0; - roadInfo.latitude = latitude; - roadInfo.longitude = longitude; - roadInfo.name = name; - if (ApplicationMode.valueOfStringKey(appModeKey, null) != null) { - roadInfo.appModeKey = appModeKey; - } else { - roadInfo.appModeKey = app.getRoutingHelper().getAppMode().getStringKey(); - } - items.add(roadInfo); - } - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); - throw new IllegalArgumentException("Json parse error", e); - } - } - - @Override - void writeItemsToJson(@NonNull JSONObject json) { - JSONArray jsonArray = new JSONArray(); - if (!items.isEmpty()) { - try { - for (AvoidRoadInfo avoidRoad : items) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("latitude", avoidRoad.latitude); - jsonObject.put("longitude", avoidRoad.longitude); - jsonObject.put("name", avoidRoad.name); - jsonObject.put("appModeKey", avoidRoad.appModeKey); - jsonArray.put(jsonObject); - } - json.put("items", jsonArray); - } catch (JSONException e) { - warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); - LOG.error("Failed write to json", e); - } - } - } - - @Nullable - @Override - SettingsItemReader getReader() { - return getJsonReader(); - } - - @Nullable - @Override - SettingsItemWriter getWriter() { - return null; - } - } - - private static class SettingsItemsFactory { - - private OsmandApplication app; - private List items = new ArrayList<>(); - - SettingsItemsFactory(@NonNull OsmandApplication app, String jsonStr) throws IllegalArgumentException, JSONException { - this.app = app; - collectItems(new JSONObject(jsonStr)); - } - - private void collectItems(JSONObject json) throws IllegalArgumentException, JSONException { - JSONArray itemsJson = json.getJSONArray("items"); - int version = json.has("version") ? json.getInt("version") : 1; - if (version > VERSION) { - throw new IllegalArgumentException("Unsupported osf version: " + version); - } - Map> pluginItems = new HashMap<>(); - for (int i = 0; i < itemsJson.length(); i++) { - JSONObject itemJson = itemsJson.getJSONObject(i); - SettingsItem item; - try { - item = createItem(itemJson); - items.add(item); - String pluginId = item.getPluginId(); - if (pluginId != null && item.getType() != SettingsItemType.PLUGIN) { - List items = pluginItems.get(pluginId); - if (items != null) { - items.add(item); - } else { - items = new ArrayList<>(); - items.add(item); - pluginItems.put(pluginId, items); - } - } - } catch (IllegalArgumentException e) { - LOG.error("Error creating item from json: " + itemJson, e); - } - } - if (items.size() == 0) { - throw new IllegalArgumentException("No items"); - } - for (SettingsItem item : items) { - if (item instanceof PluginSettingsItem) { - PluginSettingsItem pluginSettingsItem = ((PluginSettingsItem) item); - List pluginDependentItems = pluginItems.get(pluginSettingsItem.getName()); - if (!Algorithms.isEmpty(pluginDependentItems)) { - pluginSettingsItem.getPluginDependentItems().addAll(pluginDependentItems); - } - } - } - } - - @NonNull - public List getItems() { - return items; - } - - @Nullable - public SettingsItem getItemByFileName(@NonNull String fileName) { - for (SettingsItem item : items) { - if (Algorithms.stringsEqual(item.getFileName(), fileName)) { - return item; - } - } - return null; - } - - @NonNull - private SettingsItem createItem(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { - SettingsItem item = null; - SettingsItemType type = SettingsItem.parseItemType(json); - OsmandSettings settings = app.getSettings(); - switch (type) { - case GLOBAL: - item = new GlobalSettingsItem(settings); - break; - case PROFILE: - item = new ProfileSettingsItem(app, json); - break; - case PLUGIN: - item = new PluginSettingsItem(app, json); - break; - case DATA: - item = new DataSettingsItem(app, json); - break; - case FILE: - item = new FileSettingsItem(app, json); - break; - case RESOURCES: - item = new ResourcesSettingsItem(app, json); - break; - case QUICK_ACTIONS: - item = new QuickActionsSettingsItem(app, json); - break; - case POI_UI_FILTERS: - item = new PoiUiFiltersSettingsItem(app, json); - break; - case MAP_SOURCES: - item = new MapSourcesSettingsItem(app, json); - break; - case AVOID_ROADS: - item = new AvoidRoadsSettingsItem(app, json); - break; - case SUGGESTED_DOWNLOADS: - item = new SuggestedDownloadsItem(app, json); - break; - case DOWNLOADS: - item = new DownloadsItem(app, json); - break; - } - return item; - } - } - - private static class SettingsExporter { - - private Map items; - private Map additionalParams; - private boolean exportItemsFiles; - - SettingsExporter(boolean exportItemsFiles) { - this.exportItemsFiles = exportItemsFiles; - items = new LinkedHashMap<>(); - additionalParams = new LinkedHashMap<>(); - } - - void addSettingsItem(SettingsItem item) throws IllegalArgumentException { - if (items.containsKey(item.getName())) { - throw new IllegalArgumentException("Already has such item: " + item.getName()); - } - items.put(item.getName(), item); - } - - void addAdditionalParam(String key, String value) { - additionalParams.put(key, value); - } - - void exportSettings(File file) throws JSONException, IOException { - JSONObject json = createItemsJson(); - OutputStream os = new BufferedOutputStream(new FileOutputStream(file), BUFFER); - ZipOutputStream zos = new ZipOutputStream(os); - try { - ZipEntry entry = new ZipEntry("items.json"); - zos.putNextEntry(entry); - zos.write(json.toString(2).getBytes("UTF-8")); - zos.closeEntry(); - if (exportItemsFiles) { - writeItemFiles(zos); - } - zos.flush(); - zos.finish(); - } finally { - Algorithms.closeStream(zos); - Algorithms.closeStream(os); - } - } - - private void writeItemFiles(ZipOutputStream zos) throws IOException { - for (SettingsItem item : items.values()) { - SettingsItemWriter writer = item.getWriter(); - if (writer != null) { - String fileName = item.getFileName(); - if (Algorithms.isEmpty(fileName)) { - fileName = item.getDefaultFileName(); - } - ZipEntry entry = new ZipEntry(fileName); - zos.putNextEntry(entry); - writer.writeToStream(zos); - zos.closeEntry(); - } - } - } - - private JSONObject createItemsJson() throws JSONException { - JSONObject json = new JSONObject(); - json.put("version", VERSION); - for (Map.Entry param : additionalParams.entrySet()) { - json.put(param.getKey(), param.getValue()); - } - JSONArray itemsJson = new JSONArray(); - for (SettingsItem item : items.values()) { - itemsJson.put(new JSONObject(item.toJson())); - } - json.put("items", itemsJson); - return json; - } - } - - private static class SettingsImporter { - - private OsmandApplication app; - - SettingsImporter(@NonNull OsmandApplication app) { - this.app = app; - } - - List collectItems(@NonNull File file) throws IllegalArgumentException, IOException { - return processItems(file, null); - } - - void importItems(@NonNull File file, @NonNull List items) throws IllegalArgumentException, IOException { - processItems(file, items); - } - - private List getItemsFromJson(@NonNull File file) throws IOException { - List items = new ArrayList<>(); - ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); - InputStream ois = new BufferedInputStream(zis); - try { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - String fileName = checkEntryName(entry.getName()); - if (fileName.equals("items.json")) { - String itemsJson = null; - try { - itemsJson = Algorithms.readFromInputStream(ois, false).toString(); - } catch (IOException e) { - LOG.error("Error reading items.json: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } finally { - zis.closeEntry(); - } - try { - SettingsItemsFactory itemsFactory = new SettingsItemsFactory(app, itemsJson); - items.addAll(itemsFactory.getItems()); - } catch (IllegalArgumentException e) { - LOG.error("Error parsing items: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } catch (JSONException e) { - LOG.error("Error parsing items: " + itemsJson, e); - throw new IllegalArgumentException("No items"); - } - break; - } - } - } catch (IOException ex) { - LOG.error("Failed to read next entry", ex); - } finally { - Algorithms.closeStream(ois); - Algorithms.closeStream(zis); - } - return items; - } - - private List processItems(@NonNull File file, @Nullable List items) throws IllegalArgumentException, IOException { - boolean collecting = items == null; - if (collecting) { - items = getItemsFromJson(file); - } else { - if (items.size() == 0) { - throw new IllegalArgumentException("No items"); - } - } - ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); - InputStream ois = new BufferedInputStream(zis); - try { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - String fileName = checkEntryName(entry.getName()); - SettingsItem item = null; - for (SettingsItem settingsItem : items) { - if (settingsItem != null && settingsItem.applyFileName(fileName)) { - item = settingsItem; - break; - } - } - if (item != null && collecting && item.shouldReadOnCollecting() - || item != null && !collecting && !item.shouldReadOnCollecting()) { - try { - SettingsItemReader reader = item.getReader(); - if (reader != null) { - reader.readFromStream(ois); - } - } catch (IllegalArgumentException e) { - item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); - LOG.error("Error reading item data: " + item.getName(), e); - } catch (IOException e) { - item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); - LOG.error("Error reading item data: " + item.getName(), e); - } finally { - zis.closeEntry(); - } - } - } - } catch (IOException ex) { - LOG.error("Failed to read next entry", ex); - } finally { - Algorithms.closeStream(ois); - Algorithms.closeStream(zis); - } - return items; - } - - private String checkEntryName(String entryName) { - String fileExt = OSMAND_SETTINGS_FILE_EXT + "/"; - int index = entryName.indexOf(fileExt); - if (index != -1) { - entryName = entryName.substring(index + fileExt.length()); - } - return entryName; - } - } - - public static Map> getSettingsToOperate(List settingsItems, boolean importComplete) { - Map> settingsToOperate = new HashMap<>(); - List profiles = new ArrayList<>(); - List quickActions = new ArrayList<>(); - List poiUIFilters = new ArrayList<>(); - List tileSourceTemplates = new ArrayList<>(); - List routingFilesList = new ArrayList<>(); - List renderFilesList = new ArrayList<>(); - List avoidRoads = new ArrayList<>(); - for (SettingsItem item : settingsItems) { - switch (item.getType()) { - case PROFILE: - profiles.add(((ProfileSettingsItem) item).getModeBean()); - break; - case FILE: - FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { - renderFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { - routingFilesList.add(fileItem.getFile()); - } - break; - case QUICK_ACTIONS: - QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; - if (importComplete) { - quickActions.addAll(quickActionsItem.getAppliedItems()); - } else { - quickActions.addAll(quickActionsItem.getItems()); - } - break; - case POI_UI_FILTERS: - PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; - if (importComplete) { - poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); - } else { - poiUIFilters.addAll(poiUiFilterItem.getItems()); - } - break; - case MAP_SOURCES: - MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; - if (importComplete) { - tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); - } else { - tileSourceTemplates.addAll(mapSourcesItem.getItems()); - } - break; - case AVOID_ROADS: - AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; - if (importComplete) { - avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); - } else { - avoidRoads.addAll(avoidRoadsItem.getItems()); - } - break; - default: - break; - } - } - - if (!profiles.isEmpty()) { - settingsToOperate.put(ExportSettingsType.PROFILE, profiles); - } - if (!quickActions.isEmpty()) { - settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); - } - if (!poiUIFilters.isEmpty()) { - settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); - } - if (!tileSourceTemplates.isEmpty()) { - settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); - } - if (!renderFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); - } - if (!routingFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); - } - if (!avoidRoads.isEmpty()) { - settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); - } - return settingsToOperate; - } - - @SuppressLint("StaticFieldLeak") - public class ImportAsyncTask extends AsyncTask> { - - private File file; - private String latestChanges; - private int version; - - private SettingsImportListener importListener; - private SettingsCollectListener collectListener; - private CheckDuplicatesListener duplicatesListener; - private SettingsImporter importer; - - private List items = new ArrayList<>(); - private List selectedItems = new ArrayList<>(); - private List duplicates; - - private ImportType importType; - private boolean importDone; - - ImportAsyncTask(@NonNull File file, String latestChanges, int version, @Nullable SettingsCollectListener collectListener) { - this.file = file; - this.collectListener = collectListener; - this.latestChanges = latestChanges; - this.version = version; - importer = new SettingsImporter(app); - importType = ImportType.COLLECT; - } - - ImportAsyncTask(@NonNull File file, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener importListener) { - this.file = file; - this.importListener = importListener; - this.items = items; - this.latestChanges = latestChanges; - this.version = version; - importer = new SettingsImporter(app); - importType = ImportType.IMPORT; - } - - ImportAsyncTask(@NonNull File file, @NonNull List items, @NonNull List selectedItems, @Nullable CheckDuplicatesListener duplicatesListener) { - this.file = file; - this.items = items; - this.duplicatesListener = duplicatesListener; - this.selectedItems = selectedItems; - importer = new SettingsImporter(app); - importType = ImportType.CHECK_DUPLICATES; - } - - @Override - protected void onPreExecute() { - ImportAsyncTask importTask = SettingsHelper.this.importTask; - if (importTask != null && !importTask.importDone) { - finishImport(importListener, false, items); - } - SettingsHelper.this.importTask = this; - } - - @Override - protected List doInBackground(Void... voids) { - switch (importType) { - case COLLECT: - 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); - } - break; - case CHECK_DUPLICATES: - this.duplicates = getDuplicatesData(selectedItems); - return selectedItems; - case IMPORT: - return items; - } - return null; - } - - @Override - protected void onPostExecute(@Nullable List items) { - if (items != null && importType != ImportType.CHECK_DUPLICATES) { - this.items = items; - } else { - selectedItems = items; - } - switch (importType) { - case COLLECT: - importDone = true; - collectListener.onSettingsCollectFinished(true, false, this.items); - break; - case CHECK_DUPLICATES: - importDone = true; - if (duplicatesListener != null) { - duplicatesListener.onDuplicatesChecked(duplicates, selectedItems); - } - break; - case IMPORT: - if (items != null && items.size() > 0) { - for (SettingsItem item : items) { - item.apply(); - } - new ImportItemsAsyncTask(file, importListener, items).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - break; - } - } - - public List getItems() { - return items; - } - - public File getFile() { - return file; - } - - public void setImportListener(SettingsImportListener importListener) { - this.importListener = importListener; - } - - public void setDuplicatesListener(CheckDuplicatesListener duplicatesListener) { - this.duplicatesListener = duplicatesListener; - } - - ImportType getImportType() { - return importType; - } - - boolean isImportDone() { - return importDone; - } - - public List getDuplicates() { - return duplicates; - } - - public List getSelectedItems() { - return selectedItems; - } - - private List getDuplicatesData(List items) { - List duplicateItems = new ArrayList<>(); - for (SettingsItem item : items) { - if (item instanceof ProfileSettingsItem) { - if (item.exists()) { - duplicateItems.add(((ProfileSettingsItem) item).getModeBean()); - } - } else if (item instanceof CollectionSettingsItem) { - List duplicates = ((CollectionSettingsItem) item).processDuplicateItems(); - if (!duplicates.isEmpty()) { - duplicateItems.addAll(duplicates); - } - } else if (item instanceof FileSettingsItem) { - if (item.exists()) { - duplicateItems.add(((FileSettingsItem) item).getFile()); - } - } - } - return duplicateItems; - } - } - - @Nullable - public ImportAsyncTask getImportTask() { - return importTask; - } - - @Nullable - public ImportType getImportTaskType() { - ImportAsyncTask importTask = this.importTask; - return importTask != null ? importTask.getImportType() : null; - } - - public boolean isImportDone() { - ImportAsyncTask importTask = this.importTask; - return importTask == null || importTask.isImportDone(); - } - - public boolean isFileExporting(File file) { - return exportAsyncTasks.containsKey(file); - } - - public void updateExportListener(File file, SettingsExportListener listener) { - ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); - if (exportAsyncTask != null) { - exportAsyncTask.listener = listener; - } - } - - @SuppressLint("StaticFieldLeak") - private class ImportItemsAsyncTask extends AsyncTask { - - private SettingsImporter importer; - private File file; - private SettingsImportListener listener; - private List items; - - ImportItemsAsyncTask(@NonNull File file, - @Nullable SettingsImportListener listener, - @NonNull List items) { - importer = new SettingsImporter(app); - this.file = file; - this.listener = listener; - this.items = items; - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - importer.importItems(file, items); - return true; - } catch (IllegalArgumentException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - finishImport(listener, success, items); - } - } - - private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { - importTask = null; - List warnings = new ArrayList<>(); - for (SettingsItem item : items) { - warnings.addAll(item.getWarnings()); - } - if (!warnings.isEmpty()) { - app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); - } - if (listener != null) { - listener.onSettingsImportFinished(success, items); - } - } - - @SuppressLint("StaticFieldLeak") - private class ExportAsyncTask extends AsyncTask { - - private SettingsExporter exporter; - private File file; - private SettingsExportListener listener; - - ExportAsyncTask(@NonNull File settingsFile, - @Nullable SettingsExportListener listener, - @NonNull List items, boolean exportItemsFiles) { - this.file = settingsFile; - this.listener = listener; - this.exporter = new SettingsExporter(exportItemsFiles); - for (SettingsItem item : items) { - exporter.addSettingsItem(item); - } - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - exporter.exportSettings(file); - return true; - } catch (JSONException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - exportAsyncTasks.remove(file); - if (listener != null) { - listener.onSettingsExportFinished(file, success); - } - } - } - - public void collectSettings(@NonNull File settingsFile, String latestChanges, int version, - @Nullable SettingsCollectListener listener) { - new ImportAsyncTask(settingsFile, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void checkDuplicates(@NonNull File file, @NonNull List items, @NonNull List selectedItems, CheckDuplicatesListener listener) { - new ImportAsyncTask(file, items, selectedItems, 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, boolean exportItemsFiles) { - File file = new File(fileDir, fileName + OSMAND_SETTINGS_FILE_EXT); - ExportAsyncTask exportAsyncTask = new ExportAsyncTask(file, listener, items, exportItemsFiles); - exportAsyncTasks.put(file, exportAsyncTask); - exportAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, - boolean exportItemsFiles, @NonNull SettingsItem... items) { - exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items)), exportItemsFiles); - } - - public enum ImportType { - COLLECT, - CHECK_DUPLICATES, - IMPORT - } - - public List getFilteredSettingsItems(Map> additionalData, - List settingsTypes) { - List settingsItems = new ArrayList<>(); - for (ExportSettingsType settingsType : settingsTypes) { - List settingsDataObjects = additionalData.get(settingsType); - if (settingsDataObjects != null) { - for (Object object : settingsDataObjects) { - if (object instanceof ApplicationModeBean) { - settingsItems.add(new ProfileSettingsItem(app, null, (ApplicationModeBean) object)); - } - } - settingsItems.addAll(prepareAdditionalSettingsItems(new ArrayList<>(settingsDataObjects))); - } - } - return settingsItems; - } - - public Map> getAdditionalData() { - Map> dataList = new HashMap<>(); - - QuickActionRegistry registry = app.getQuickActionRegistry(); - List actionsList = registry.getQuickActions(); - if (!actionsList.isEmpty()) { - dataList.put(ExportSettingsType.QUICK_ACTIONS, actionsList); - } - - List poiList = app.getPoiFilters().getUserDefinedPoiFilters(false); - if (!poiList.isEmpty()) { - dataList.put(ExportSettingsType.POI_TYPES, poiList); - } - - List iTileSources = new ArrayList<>(); - Set tileSourceNames = app.getSettings().getTileSourceEntries(true).keySet(); - for (String name : tileSourceNames) { - File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + name); - 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.getUrlTemplate() != null) { - iTileSources.add(template); - } - } - } - if (!iTileSources.isEmpty()) { - dataList.put(ExportSettingsType.MAP_SOURCES, iTileSources); - } - - Map externalRenderers = app.getRendererRegistry().getExternalRenderers(); - if (!externalRenderers.isEmpty()) { - dataList.put(ExportSettingsType.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.put(ExportSettingsType.CUSTOM_ROUTING, Arrays.asList(fl)); - } - } - - Map impassableRoads = app.getAvoidSpecificRoads().getImpassableRoads(); - if (!impassableRoads.isEmpty()) { - dataList.put(ExportSettingsType.AVOID_ROADS, new ArrayList<>(impassableRoads.values())); - } - return dataList; - } - - public List prepareAdditionalSettingsItems(List data) { - List settingsItems = new ArrayList<>(); - List quickActions = new ArrayList<>(); - List poiUIFilters = new ArrayList<>(); - List tileSourceTemplates = new ArrayList<>(); - List avoidRoads = new ArrayList<>(); - for (Object object : data) { - if (object instanceof QuickAction) { - quickActions.add((QuickAction) object); - } else if (object instanceof PoiUIFilter) { - poiUIFilters.add((PoiUIFilter) object); - } else if (object instanceof TileSourceTemplate || object instanceof SQLiteTileSource) { - tileSourceTemplates.add((ITileSource) object); - } else if (object instanceof File) { - try { - settingsItems.add(new FileSettingsItem(app, (File) object)); - } catch (IllegalArgumentException e) { - LOG.warn("Trying to export unsuported file type", e); - } - } else if (object instanceof AvoidRoadInfo) { - avoidRoads.add((AvoidRoadInfo) object); - } - } - if (!quickActions.isEmpty()) { - settingsItems.add(new QuickActionsSettingsItem(app, quickActions)); - } - if (!poiUIFilters.isEmpty()) { - settingsItems.add(new PoiUiFiltersSettingsItem(app, poiUIFilters)); - } - if (!tileSourceTemplates.isEmpty()) { - settingsItems.add(new MapSourcesSettingsItem(app, tileSourceTemplates)); - } - if (!avoidRoads.isEmpty()) { - settingsItems.add(new AvoidRoadsSettingsItem(app, avoidRoads)); - } - return settingsItems; - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java new file mode 100644 index 0000000000..3e03fa04fc --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java @@ -0,0 +1,179 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.helpers.AvoidSpecificRoads; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class AvoidRoadsSettingsItem extends CollectionSettingsItem { + + private OsmandSettings settings; + private AvoidSpecificRoads specificRoads; + + public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @Nullable AvoidRoadsSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + AvoidRoadsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + settings = app.getSettings(); + specificRoads = app.getAvoidSpecificRoads(); + existingItems = new ArrayList<>(specificRoads.getImpassableRoads().values()); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.AVOID_ROADS; + } + + @NonNull + @Override + public String getName() { + return "avoid_roads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "avoid_roads"; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + for (AvoidSpecificRoads.AvoidRoadInfo duplicate : duplicateItems) { + if (shouldReplace) { + LatLon latLon = new LatLon(duplicate.latitude, duplicate.longitude); + if (settings.removeImpassableRoad(latLon)) { + settings.addImpassableRoad(duplicate); + } + } else { + settings.addImpassableRoad(renameItem(duplicate)); + } + } + for (AvoidSpecificRoads.AvoidRoadInfo avoidRoad : appliedItems) { + settings.addImpassableRoad(avoidRoad); + } + specificRoads.loadImpassableRoads(); + specificRoads.initRouteObjects(true); + } + } + + @Override + public boolean isDuplicate(@NonNull AvoidSpecificRoads.AvoidRoadInfo item) { + return existingItems.contains(item); + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @NonNull + @Override + public AvoidSpecificRoads.AvoidRoadInfo renameItem(@NonNull AvoidSpecificRoads.AvoidRoadInfo item) { + int number = 0; + while (true) { + number++; + AvoidSpecificRoads.AvoidRoadInfo renamedItem = new AvoidSpecificRoads.AvoidRoadInfo(); + renamedItem.name = item.name + "_" + number; + if (!isDuplicate(renamedItem)) { + renamedItem.id = item.id; + renamedItem.latitude = item.latitude; + renamedItem.longitude = item.longitude; + renamedItem.appModeKey = item.appModeKey; + return renamedItem; + } + } + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + double latitude = object.optDouble("latitude"); + double longitude = object.optDouble("longitude"); + String name = object.optString("name"); + String appModeKey = object.optString("appModeKey"); + AvoidSpecificRoads.AvoidRoadInfo roadInfo = new AvoidSpecificRoads.AvoidRoadInfo(); + roadInfo.id = 0; + roadInfo.latitude = latitude; + roadInfo.longitude = longitude; + roadInfo.name = name; + if (ApplicationMode.valueOfStringKey(appModeKey, null) != null) { + roadInfo.appModeKey = appModeKey; + } else { + roadInfo.appModeKey = app.getRoutingHelper().getAppMode().getStringKey(); + } + items.add(roadInfo); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (AvoidSpecificRoads.AvoidRoadInfo avoidRoad : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("latitude", avoidRoad.latitude); + jsonObject.put("longitude", avoidRoad.longitude); + jsonObject.put("name", avoidRoad.name); + jsonObject.put("appModeKey", avoidRoad.appModeKey); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java new file mode 100644 index 0000000000..dea9d51bc8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/CollectionSettingsItem.java @@ -0,0 +1,75 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public abstract class CollectionSettingsItem extends SettingsItem { + + protected List items; + protected List appliedItems; + protected List duplicateItems; + protected List existingItems; + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + appliedItems = new ArrayList<>(); + duplicateItems = new ArrayList<>(); + } + + CollectionSettingsItem(@NonNull OsmandApplication app, @Nullable CollectionSettingsItem baseItem, @NonNull List items) { + super(app, baseItem); + this.items = items; + } + + CollectionSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @NonNull + public List getItems() { + return items; + } + + @NonNull + public List getAppliedItems() { + return appliedItems; + } + + @NonNull + public List getDuplicateItems() { + return duplicateItems; + } + + @NonNull + public List processDuplicateItems() { + if (!items.isEmpty()) { + for (T item : items) { + if (isDuplicate(item)) { + duplicateItems.add(item); + } + } + } + return duplicateItems; + } + + public List getNewItems() { + List res = new ArrayList<>(items); + res.removeAll(duplicateItems); + return res; + } + + public abstract boolean isDuplicate(@NonNull T item); + + @NonNull + public abstract T renameItem(@NonNull T item); +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java new file mode 100644 index 0000000000..59fd6d6538 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DataSettingsItem.java @@ -0,0 +1,87 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class DataSettingsItem extends StreamSettingsItem { + + @Nullable + private byte[] data; + + public DataSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { + super(app, name); + } + + DataSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + public DataSettingsItem(@NonNull OsmandApplication app, @NonNull byte[] data, @NonNull String name) { + super(app, name); + this.data = data; + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.DATA; + } + + @NonNull + @Override + public String getDefaultFileExtension() { + return ".dat"; + } + + @Nullable + public byte[] getData() { + return data; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName)) { + name = Algorithms.getFileNameWithoutExtension(new File(fileName)); + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new StreamSettingsItemReader(this) { + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[SettingsHelper.BUFFER]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + DataSettingsItem.this.data = buffer.toByteArray(); + } + }; + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + setInputStream(new ByteArrayInputStream(data)); + return super.getWriter(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java new file mode 100644 index 0000000000..14e6b1da8d --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/DownloadsItem.java @@ -0,0 +1,102 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.map.WorldRegion; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.CustomRegion; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class DownloadsItem extends SettingsItem { + + private List items; + + DownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.DOWNLOADS; + + } + + @NonNull + @Override + public String getName() { + return "downloads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "downloads"; + } + + public List getItems() { + return items; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + items.addAll(CustomOsmandPlugin.collectRegionsFromJson(app, jsonArray)); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (WorldRegion region : items) { + if (region instanceof CustomRegion) { + JSONObject regionJson = ((CustomRegion) region).toJson(); + jsonArray.put(regionJson); + } + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java new file mode 100644 index 0000000000..765ecebf40 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java @@ -0,0 +1,234 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileSettingsItem extends StreamSettingsItem { + + public enum FileSubtype { + UNKNOWN("", null), + OTHER("other", ""), + ROUTING_CONFIG("routing_config", IndexConstants.ROUTING_PROFILES_DIR), + RENDERING_STYLE("rendering_style", IndexConstants.RENDERERS_DIR), + OBF_MAP("obf_map", IndexConstants.MAPS_PATH), + TILES_MAP("tiles_map", IndexConstants.TILES_INDEX_DIR), + GPX("gpx", IndexConstants.GPX_INDEX_DIR), + VOICE("voice", IndexConstants.VOICE_INDEX_DIR), + TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR); + + private String subtypeName; + private String subtypeFolder; + + FileSubtype(String subtypeName, String subtypeFolder) { + this.subtypeName = subtypeName; + this.subtypeFolder = subtypeFolder; + } + + public String getSubtypeName() { + return subtypeName; + } + + public String getSubtypeFolder() { + return subtypeFolder; + } + + public static FileSubtype getSubtypeByName(@NonNull String name) { + for (FileSubtype subtype : FileSubtype.values()) { + if (name.equals(subtype.subtypeName)) { + return subtype; + } + } + return null; + } + + public static FileSubtype getSubtypeByFileName(@NonNull String fileName) { + String name = fileName; + if (fileName.startsWith(File.separator)) { + name = fileName.substring(1); + } + for (FileSubtype subtype : FileSubtype.values()) { + switch (subtype) { + case UNKNOWN: + case OTHER: + break; + case OBF_MAP: + if (name.endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) { + return subtype; + } + break; + default: + if (name.startsWith(subtype.subtypeFolder)) { + return subtype; + } + break; + } + } + return UNKNOWN; + } + + @NonNull + @Override + public String toString() { + return subtypeName; + } + } + + protected File file; + private File appPath; + protected FileSubtype subtype; + + public FileSettingsItem(@NonNull OsmandApplication app, @NonNull File file) throws IllegalArgumentException { + super(app, file.getPath().replace(app.getAppPath(null).getPath(), "")); + this.file = file; + this.appPath = app.getAppPath(null); + String fileName = getFileName(); + if (fileName != null) { + this.subtype = FileSubtype.getSubtypeByFileName(fileName); + } + if (subtype == FileSubtype.UNKNOWN || subtype == null) { + throw new IllegalArgumentException("Unknown file subtype: " + fileName); + } + } + + FileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + this.appPath = app.getAppPath(null); + if (subtype == FileSubtype.OTHER) { + this.file = new File(appPath, name); + } else if (subtype == FileSubtype.UNKNOWN || subtype == null) { + throw new IllegalArgumentException("Unknown file subtype: " + getFileName()); + } else { + this.file = new File(app.getAppPath(subtype.subtypeFolder), name); + } + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.FILE; + } + + public File getPluginPath() { + String pluginId = getPluginId(); + if (!Algorithms.isEmpty(pluginId)) { + return new File(appPath, IndexConstants.PLUGINS_DIR + pluginId); + } + return appPath; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String fileName = getFileName(); + if (subtype == null) { + String subtypeStr = json.has("subtype") ? json.getString("subtype") : null; + if (!Algorithms.isEmpty(subtypeStr)) { + subtype = FileSubtype.getSubtypeByName(subtypeStr); + } else if (!Algorithms.isEmpty(fileName)) { + subtype = FileSubtype.getSubtypeByFileName(fileName); + } else { + subtype = FileSubtype.UNKNOWN; + } + } + if (!Algorithms.isEmpty(fileName)) { + if (subtype == FileSubtype.OTHER) { + name = fileName; + } else if (subtype != null && subtype != FileSubtype.UNKNOWN) { + name = Algorithms.getFileWithoutDirs(fileName); + } + } + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + if (subtype != null) { + json.put("subtype", subtype.getSubtypeName()); + } + } + + @NonNull + public File getFile() { + return file; + } + + @NonNull + public FileSubtype getSubtype() { + return subtype; + } + + @Override + public boolean exists() { + return file.exists(); + } + + private File renameFile(File file) { + int number = 0; + String path = file.getAbsolutePath(); + while (true) { + number++; + String copyName = path.replaceAll(file.getName(), file.getName().replaceFirst("[.]", "_" + number + ".")); + File copyFile = new File(copyName); + if (!copyFile.exists()) { + return copyFile; + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new StreamSettingsItemReader(this) { + @Override + public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException { + OutputStream output; + File dest = FileSettingsItem.this.file; + if (dest.exists() && !shouldReplace) { + dest = renameFile(dest); + } + if (dest.getParentFile() != null && !dest.getParentFile().exists()) { + dest.getParentFile().mkdirs(); + } + output = new FileOutputStream(dest); + byte[] buffer = new byte[SettingsHelper.BUFFER]; + int count; + try { + while ((count = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, count); + } + output.flush(); + } finally { + Algorithms.closeStream(output); + } + } + }; + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + try { + setInputStream(new FileInputStream(file)); + } catch (FileNotFoundException e) { + warnings.add(app.getString(R.string.settings_item_read_error, file.getName())); + SettingsHelper.LOG.error("Failed to set input stream from file: " + file.getName(), e); + } + return super.getWriter(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java new file mode 100644 index 0000000000..a1d3ef785a --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/GlobalSettingsItem.java @@ -0,0 +1,65 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.R; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +public class GlobalSettingsItem extends OsmandSettingsItem { + + public GlobalSettingsItem(@NonNull OsmandSettings settings) { + super(settings); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.GLOBAL; + } + + @NonNull + @Override + public String getName() { + return "general_settings"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return ctx.getString(R.string.general_settings_2); + } + + @Override + public boolean exists() { + return true; + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + preference.readFromJson(json, null); + } + }; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + preference.writeToJson(json, null); + } + }; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java new file mode 100644 index 0000000000..0ff8eb4a48 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/MapSourcesSettingsItem.java @@ -0,0 +1,241 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MapSourcesSettingsItem extends CollectionSettingsItem { + + private List existingItemsNames; + + public MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public MapSourcesSettingsItem(@NonNull OsmandApplication app, @Nullable MapSourcesSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + MapSourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + existingItemsNames = new ArrayList<>(app.getSettings().getTileSourceEntries().values()); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.MAP_SOURCES; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + if (shouldReplace) { + for (ITileSource tileSource : duplicateItems) { + if (tileSource instanceof SQLiteTileSource) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName() + IndexConstants.SQLITE_EXT); + if (f != null && f.exists() && Algorithms.removeAllFiles(f)) { + appliedItems.add(tileSource); + } + } else if (tileSource instanceof TileSourceManager.TileSourceTemplate) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + tileSource.getName()); + if (f != null && f.exists() && f.isDirectory() && Algorithms.removeAllFiles(f)) { + appliedItems.add(tileSource); + } + } + } + } else { + for (ITileSource tileSource : duplicateItems) { + appliedItems.add(renameItem(tileSource)); + } + } + for (ITileSource tileSource : appliedItems) { + if (tileSource instanceof TileSourceManager.TileSourceTemplate) { + app.getSettings().installTileSource((TileSourceManager.TileSourceTemplate) tileSource); + } else if (tileSource instanceof SQLiteTileSource) { + ((SQLiteTileSource) tileSource).createDataBase(); + } + } + } + } + + @NonNull + @Override + public ITileSource renameItem(@NonNull ITileSource item) { + int number = 0; + while (true) { + number++; + if (item instanceof SQLiteTileSource) { + SQLiteTileSource oldItem = (SQLiteTileSource) item; + SQLiteTileSource renamedItem = new SQLiteTileSource( + oldItem, + oldItem.getName() + "_" + number, + app); + if (!isDuplicate(renamedItem)) { + return renamedItem; + } + } else if (item instanceof TileSourceManager.TileSourceTemplate) { + TileSourceManager.TileSourceTemplate oldItem = (TileSourceManager.TileSourceTemplate) item; + oldItem.setName(oldItem.getName() + "_" + number); + if (!isDuplicate(oldItem)) { + return oldItem; + } + } + } + } + + @Override + public boolean isDuplicate(@NonNull ITileSource item) { + for (String name : existingItemsNames) { + if (name.equals(item.getName())) { + return true; + } + } + return false; + } + + @NonNull + @Override + public String getName() { + return "map_sources"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "map_sources"; + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.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"); + String userAgent = object.optString("userAgent"); + boolean timeSupported = object.optBoolean("timesupported", false); + long expire = object.optLong("expire", -1); + 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"); + + if (expire > 0 && expire < 3600000) { + expire = expire * 60 * 1000L; + } + + ITileSource template; + if (!sql) { + TileSourceManager.TileSourceTemplate tileSourceTemplate = new TileSourceManager.TileSourceTemplate(name, url, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); + tileSourceTemplate.setRule(rule); + tileSourceTemplate.setRandoms(randoms); + tileSourceTemplate.setReferer(referer); + tileSourceTemplate.setUserAgent(userAgent); + tileSourceTemplate.setEllipticYTile(ellipsoid); + tileSourceTemplate.setInvertedYTile(invertedY); + tileSourceTemplate.setExpirationTimeMillis(timeSupported ? expire : -1); + + template = tileSourceTemplate; + } else { + template = new SQLiteTileSource(app, name, minZoom, maxZoom, url, randoms, ellipsoid, invertedY, referer, userAgent, timeSupported, expire, inversiveZoom, rule); + } + items.add(template); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (ITileSource template : items) { + 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("userAgent", template.getUserAgent()); + jsonObject.put("timesupported", template.isTimeSupported()); + jsonObject.put("expire", template.getExpirationTimeMinutes()); + 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()); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java new file mode 100644 index 0000000000..b59e89018f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java @@ -0,0 +1,33 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +public abstract class OsmandSettingsItem extends SettingsItem { + + private OsmandSettings settings; + + protected OsmandSettingsItem(@NonNull OsmandSettings settings) { + super(settings.getContext()); + this.settings = settings; + } + + protected OsmandSettingsItem(@NonNull OsmandSettings settings, @Nullable OsmandSettingsItem baseItem) { + super(settings.getContext(), baseItem); + this.settings = settings; + } + + protected OsmandSettingsItem(@NonNull SettingsHelper.SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { + super(settings.getContext(), json); + this.settings = settings; + } + + public OsmandSettings getSettings() { + return settings; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java new file mode 100644 index 0000000000..0267e69471 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemReader.java @@ -0,0 +1,78 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.Map; + +public abstract class OsmandSettingsItemReader extends SettingsItemReader { + + private OsmandSettings settings; + + public OsmandSettingsItemReader(@NonNull T item, @NonNull OsmandSettings settings) { + super(item); + this.settings = settings; + } + + protected abstract 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 { + json = new JSONObject(jsonStr); + } catch (JSONException e) { + throw new IllegalArgumentException("Json parse error", e); + } + readPreferencesFromJson(json); + } + + void readPreferencesFromJson(final JSONObject json) { + settings.getContext().runInUIThread(new Runnable() { + @Override + public void run() { + Map> prefs = settings.getRegisteredPreferences(); + Iterator iter = json.keys(); + while (iter.hasNext()) { + String key = iter.next(); + OsmandPreference p = prefs.get(key); + if (p != null) { + try { + readPreferenceFromJson(p, json); + } catch (Exception e) { + SettingsHelper.LOG.error("Failed to read preference: " + key, e); + } + } else { + SettingsHelper.LOG.warn("No preference while importing settings: " + key); + } + } + } + }); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java new file mode 100644 index 0000000000..810ae90f31 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItemWriter.java @@ -0,0 +1,49 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; + +public abstract class OsmandSettingsItemWriter extends SettingsItemWriter { + + private OsmandSettings settings; + + public OsmandSettingsItemWriter(@NonNull T item, @NonNull OsmandSettings settings) { + super(item); + this.settings = settings; + } + + protected abstract void writePreferenceToJson(@NonNull OsmandPreference preference, + @NonNull JSONObject json) throws JSONException; + + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + JSONObject json = new JSONObject(); + Map> prefs = settings.getRegisteredPreferences(); + for (OsmandPreference pref : prefs.values()) { + try { + writePreferenceToJson(pref, json); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write preference: " + pref.getId(), e); + } + } + if (json.length() > 0) { + try { + String s = json.toString(2); + outputStream.write(s.getBytes("UTF-8")); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write json to stream", e); + } + return true; + } + return false; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java new file mode 100644 index 0000000000..335d8952f3 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java @@ -0,0 +1,116 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class PluginSettingsItem extends SettingsItem { + + private CustomOsmandPlugin plugin; + private List pluginDependentItems; + + PluginSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + pluginDependentItems = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.PLUGIN; + } + + @NonNull + @Override + public String getName() { + return plugin.getId(); + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return plugin.getName(); + } + + @NonNull + @Override + public String getDefaultFileName() { + return getName(); + } + + public CustomOsmandPlugin getPlugin() { + return plugin; + } + + public List getPluginDependentItems() { + return pluginDependentItems; + } + + @Override + public boolean exists() { + return OsmandPlugin.getPlugin(getPluginId()) != null; + } + + @Override + public void apply() { + if (shouldReplace || !exists()) { + for (SettingsItem item : pluginDependentItems) { + if (item instanceof FileSettingsItem) { + FileSettingsItem fileItem = (FileSettingsItem) item; + if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + plugin.addRenderer(fileItem.getName()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + plugin.addRouter(fileItem.getName()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.OTHER) { + plugin.setResourceDirName(item.getFileName()); + } + } else if (item instanceof SuggestedDownloadsItem) { + plugin.updateSuggestedDownloads(((SuggestedDownloadsItem) item).getItems()); + } else if (item instanceof DownloadsItem) { + plugin.updateDownloadItems(((DownloadsItem) item).getItems()); + } + } + OsmandPlugin.addCustomPlugin(app, plugin); + } + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + plugin = new CustomOsmandPlugin(app, json); + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + plugin.writeAdditionalDataToJson(json); + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java new file mode 100644 index 0000000000..a97c761f39 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java @@ -0,0 +1,178 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.osm.MapPoiTypes; +import net.osmand.osm.PoiCategory; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.poi.PoiUIFilter; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +public class PoiUiFiltersSettingsItem extends CollectionSettingsItem { + + public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @Nullable PoiUiFiltersSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + PoiUiFiltersSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + existingItems = app.getPoiFilters().getUserDefinedPoiFilters(false); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.POI_UI_FILTERS; + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + + for (PoiUIFilter duplicate : duplicateItems) { + appliedItems.add(shouldReplace ? duplicate : renameItem(duplicate)); + } + for (PoiUIFilter filter : appliedItems) { + app.getPoiFilters().createPoiFilter(filter, false); + } + app.getSearchUICore().refreshCustomPoiFilters(); + } + } + + @Override + public boolean isDuplicate(@NonNull PoiUIFilter item) { + String savedName = item.getName(); + for (PoiUIFilter filter : existingItems) { + if (filter.getName().equals(savedName)) { + return true; + } + } + return false; + } + + @NonNull + @Override + public PoiUIFilter renameItem(@NonNull PoiUIFilter item) { + int number = 0; + while (true) { + number++; + PoiUIFilter renamedItem = new PoiUIFilter(item, + item.getName() + "_" + number, + item.getFilterId() + "_" + number); + if (!isDuplicate(renamedItem)) { + return renamedItem; + } + } + } + + @NonNull + @Override + public String getName() { + return "poi_ui_filters"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "poi_ui_filters"; + } + + @Override + public boolean shouldReadOnCollecting() { + return true; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + MapPoiTypes poiTypes = app.getPoiTypes(); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.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); + items.add(filter); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>>() { + }.getType(); + if (!items.isEmpty()) { + try { + for (PoiUIFilter filter : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", filter.getName()); + jsonObject.put("filterId", filter.getFilterId()); + jsonObject.put("acceptedTypes", gson.toJson(filter.getAcceptedTypes(), type)); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java new file mode 100644 index 0000000000..0e2e1c8f8a --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ProfileSettingsItem.java @@ -0,0 +1,312 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.IndexConstants; +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmandPreference; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.router.GeneralRouter; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ProfileSettingsItem extends OsmandSettingsItem { + + private ApplicationMode appMode; + private ApplicationMode.ApplicationModeBuilder builder; + private ApplicationMode.ApplicationModeBean modeBean; + + private JSONObject additionalPrefsJson; + private Set appModeBeanPrefsIds; + + public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull ApplicationMode appMode) { + super(app.getSettings()); + this.appMode = appMode; + } + + public ProfileSettingsItem(@NonNull OsmandApplication app, @Nullable ProfileSettingsItem baseItem, @NonNull ApplicationMode.ApplicationModeBean modeBean) { + super(app.getSettings(), baseItem); + this.modeBean = modeBean; + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = builder.getApplicationMode(); + } + + public ProfileSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(SettingsHelper.SettingsItemType.PROFILE, app.getSettings(), json); + } + + @Override + protected void init() { + super.init(); + appModeBeanPrefsIds = new HashSet<>(Arrays.asList(getAppModeBeanPrefsIds())); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.PROFILE; + } + + public ApplicationMode getAppMode() { + return appMode; + } + + public ApplicationMode.ApplicationModeBean getModeBean() { + return modeBean; + } + + @NonNull + @Override + public String getName() { + return appMode.getStringKey(); + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + if (appMode.isCustomProfile()) { + return modeBean.userProfileName; + } else if (appMode.getNameKeyResource() != -1) { + return ctx.getString(appMode.getNameKeyResource()); + } else { + return getName(); + } + } + + @NonNull + @Override + public String getDefaultFileName() { + return "profile_" + getName() + getDefaultFileExtension(); + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + String appModeJson = json.getString("appMode"); + modeBean = ApplicationMode.fromJson(appModeJson); + builder = ApplicationMode.fromModeBean(app, modeBean); + ApplicationMode appMode = builder.getApplicationMode(); + if (!appMode.isCustomProfile()) { + appMode = ApplicationMode.valueOfStringKey(appMode.getStringKey(), appMode); + } + this.appMode = appMode; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + additionalPrefsJson = json.optJSONObject("prefs"); + } + + @Override + public boolean exists() { + return builder != null && ApplicationMode.valueOfStringKey(getName(), null) != null; + } + + private void renameProfile() { + List values = ApplicationMode.allPossibleValues(); + if (Algorithms.isEmpty(modeBean.userProfileName)) { + ApplicationMode appMode = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); + if (appMode != null) { + modeBean.userProfileName = app.getString(appMode.getNameKeyResource()); + } + } + int number = 0; + while (true) { + number++; + String key = modeBean.stringKey + "_" + number; + String name = modeBean.userProfileName + '_' + number; + if (ApplicationMode.valueOfStringKey(key, null) == null && isNameUnique(values, name)) { + modeBean.userProfileName = name; + modeBean.stringKey = key; + break; + } + } + } + + private boolean isNameUnique(List values, String name) { + for (ApplicationMode mode : values) { + if (mode.getUserProfileName().equals(name)) { + return false; + } + } + return true; + } + + @Override + public void apply() { + if (!appMode.isCustomProfile() && !shouldReplace) { + ApplicationMode parent = ApplicationMode.valueOfStringKey(modeBean.stringKey, null); + renameProfile(); + ApplicationMode.ApplicationModeBuilder builder = ApplicationMode + .createCustomMode(parent, modeBean.stringKey, app) + .setIconResName(modeBean.iconName) + .setUserProfileName(modeBean.userProfileName) + .setRoutingProfile(modeBean.routingProfile) + .setRouteService(modeBean.routeService) + .setIconColor(modeBean.iconColor) + .setLocationIcon(modeBean.locIcon) + .setNavigationIcon(modeBean.navIcon); + app.getSettings().copyPreferencesFromProfile(parent, builder.getApplicationMode()); + appMode = ApplicationMode.saveProfile(builder, app); + } else if (!shouldReplace && exists()) { + renameProfile(); + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = ApplicationMode.saveProfile(builder, app); + } else { + builder = ApplicationMode.fromModeBean(app, modeBean); + appMode = ApplicationMode.saveProfile(builder, app); + } + ApplicationMode.changeProfileAvailability(appMode, true, app); + } + + public void applyAdditionalPrefs() { + if (additionalPrefsJson != null) { + updatePluginResPrefs(); + + SettingsItemReader reader = getReader(); + if (reader instanceof OsmandSettingsItemReader) { + ((OsmandSettingsItemReader) reader).readPreferencesFromJson(additionalPrefsJson); + } + } + } + + private void updatePluginResPrefs() { + String pluginId = getPluginId(); + if (Algorithms.isEmpty(pluginId)) { + return; + } + OsmandPlugin plugin = OsmandPlugin.getPlugin(pluginId); + if (plugin instanceof CustomOsmandPlugin) { + CustomOsmandPlugin customPlugin = (CustomOsmandPlugin) plugin; + String resDirPath = IndexConstants.PLUGINS_DIR + pluginId + "/" + customPlugin.getResourceDirName(); + + for (Iterator it = additionalPrefsJson.keys(); it.hasNext(); ) { + try { + String prefId = it.next(); + Object value = additionalPrefsJson.get(prefId); + if (value instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) value; + for (Iterator iterator = jsonObject.keys(); iterator.hasNext(); ) { + String key = iterator.next(); + Object val = jsonObject.get(key); + if (val instanceof String) { + val = checkPluginResPath((String) val, resDirPath); + } + jsonObject.put(key, val); + } + } else if (value instanceof String) { + value = checkPluginResPath((String) value, resDirPath); + additionalPrefsJson.put(prefId, value); + } + } catch (JSONException e) { + SettingsHelper.LOG.error(e); + } + } + } + } + + private String checkPluginResPath(String path, String resDirPath) { + if (path.startsWith("@")) { + return resDirPath + "/" + path.substring(1); + } + return path; + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + json.put("appMode", new JSONObject(appMode.toJson())); + } + + @Nullable + @Override + SettingsItemReader getReader() { + return new OsmandSettingsItemReader(this, getSettings()) { + @Override + protected void readPreferenceFromJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + if (!appModeBeanPrefsIds.contains(preference.getId())) { + preference.readFromJson(json, appMode); + } + } + + @Override + void readPreferencesFromJson(final JSONObject json) { + getSettings().getContext().runInUIThread(new Runnable() { + @Override + public void run() { + OsmandSettings settings = getSettings(); + Map> prefs = settings.getRegisteredPreferences(); + Iterator iter = json.keys(); + while (iter.hasNext()) { + String key = iter.next(); + OsmandPreference p = prefs.get(key); + if (p == null) { + if (OsmandSettings.isRoutingPreference(key)) { + p = settings.registerStringPreference(key, ""); + } + } + if (p != null) { + try { + readPreferenceFromJson(p, json); + if (OsmandSettings.isRoutingPreference(p.getId())) { + if (p.getId().endsWith(GeneralRouter.USE_SHORTEST_WAY)) { + settings.FAST_ROUTE_MODE.setModeValue(appMode, + !settings.getCustomRoutingBooleanProperty(GeneralRouter.USE_SHORTEST_WAY, false).getModeValue(appMode)); + } + } + } catch (Exception e) { + SettingsHelper.LOG.error("Failed to read preference: " + key, e); + } + } else { + SettingsHelper.LOG.warn("No preference while importing settings: " + key); + } + } + } + }); + } + }; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return new OsmandSettingsItemWriter(this, getSettings()) { + @Override + protected void writePreferenceToJson(@NonNull OsmandPreference preference, @NonNull JSONObject json) throws JSONException { + if (!appModeBeanPrefsIds.contains(preference.getId())) { + preference.writeToJson(json, appMode); + } + } + }; + } + + private String[] getAppModeBeanPrefsIds() { + OsmandSettings settings = app.getSettings(); + return new String[] { + settings.ICON_COLOR.getId(), + settings.ICON_RES_NAME.getId(), + settings.PARENT_APP_MODE.getId(), + settings.ROUTING_PROFILE.getId(), + settings.ROUTE_SERVICE.getId(), + settings.USER_PROFILE_NAME.getId(), + settings.LOCATION_ICON.getId(), + settings.NAVIGATION_ICON.getId(), + settings.APP_MODE_ORDER.getId() + }; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java new file mode 100644 index 0000000000..64d23166dd --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/QuickActionsSettingsItem.java @@ -0,0 +1,183 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionRegistry; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class QuickActionsSettingsItem extends CollectionSettingsItem { + + private QuickActionRegistry actionRegistry; + + public QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull List items) { + super(app, null, items); + } + + public QuickActionsSettingsItem(@NonNull OsmandApplication app, @Nullable QuickActionsSettingsItem baseItem, @NonNull List items) { + super(app, baseItem, items); + } + + QuickActionsSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + actionRegistry = app.getQuickActionRegistry(); + existingItems = actionRegistry.getQuickActions(); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.QUICK_ACTIONS; + } + + @Override + public boolean isDuplicate(@NonNull QuickAction item) { + return !actionRegistry.isNameUnique(item, app); + } + + @NonNull + @Override + public QuickAction renameItem(@NonNull QuickAction item) { + return actionRegistry.generateUniqueName(item, app); + } + + @Override + public void apply() { + List newItems = getNewItems(); + if (!newItems.isEmpty() || !duplicateItems.isEmpty()) { + appliedItems = new ArrayList<>(newItems); + List newActions = new ArrayList<>(existingItems); + if (!duplicateItems.isEmpty()) { + if (shouldReplace) { + for (QuickAction duplicateItem : duplicateItems) { + for (QuickAction savedAction : existingItems) { + if (duplicateItem.getName(app).equals(savedAction.getName(app))) { + newActions.remove(savedAction); + } + } + } + } else { + for (QuickAction duplicateItem : duplicateItems) { + renameItem(duplicateItem); + } + } + appliedItems.addAll(duplicateItems); + } + newActions.addAll(appliedItems); + actionRegistry.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"; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + QuickActionRegistry quickActionRegistry = app.getQuickActionRegistry(); + JSONArray itemsJson = json.getJSONArray("items"); + for (int i = 0; i < itemsJson.length(); i++) { + JSONObject object = itemsJson.getJSONObject(i); + String name = object.getString("name"); + QuickAction quickAction = null; + if (object.has("actionType")) { + quickAction = quickActionRegistry.newActionByStringType(object.getString("actionType")); + } else if (object.has("type")) { + quickAction = quickActionRegistry.newActionByType(object.getInt("type")); + } + if (quickAction != null) { + String paramsString = object.getString("params"); + HashMap params = gson.fromJson(paramsString, type); + + if (!name.isEmpty()) { + quickAction.setName(name); + } + quickAction.setParams(params); + items.add(quickAction); + } else { + warnings.add(app.getString(R.string.settings_item_read_error, name)); + } + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + if (!items.isEmpty()) { + try { + for (QuickAction action : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", action.hasCustomName(app) + ? action.getName(app) : ""); + jsonObject.put("actionType", action.getActionType().getStringId()); + jsonObject.put("params", gson.toJson(action.getParams(), type)); + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return getJsonReader(); + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java new file mode 100644 index 0000000000..ced9c1d885 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/ResourcesSettingsItem.java @@ -0,0 +1,72 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; + +public class ResourcesSettingsItem extends FileSettingsItem { + + ResourcesSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + shouldReplace = true; + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName) && !fileName.endsWith(File.separator)) { + this.fileName = fileName + File.separator; + } + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.RESOURCES; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + subtype = FileSubtype.OTHER; + super.readFromJson(json); + } + + @Override + void writeToJson(@NonNull JSONObject json) throws JSONException { + super.writeToJson(json); + String fileName = getFileName(); + if (!Algorithms.isEmpty(fileName)) { + if (fileName.endsWith(File.separator)) { + fileName = fileName.substring(0, fileName.length() - 1); + } + json.put("file", fileName); + } + } + + @Override + public boolean applyFileName(@NonNull String fileName) { + if (fileName.endsWith(File.separator)) { + return false; + } + String itemFileName = getFileName(); + if (itemFileName != null && itemFileName.endsWith(File.separator)) { + if (fileName.startsWith(itemFileName)) { + this.file = new File(getPluginPath(), fileName); + return true; + } else { + return false; + } + } else { + return super.applyFileName(fileName); + } + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java new file mode 100644 index 0000000000..ccc23b5416 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsExporter.java @@ -0,0 +1,91 @@ +package net.osmand.plus.settings.backend.backup; + +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +class SettingsExporter { + + private Map items; + private Map additionalParams; + private boolean exportItemsFiles; + + SettingsExporter(boolean exportItemsFiles) { + this.exportItemsFiles = exportItemsFiles; + items = new LinkedHashMap<>(); + additionalParams = new LinkedHashMap<>(); + } + + void addSettingsItem(SettingsItem item) throws IllegalArgumentException { + if (items.containsKey(item.getName())) { + throw new IllegalArgumentException("Already has such item: " + item.getName()); + } + items.put(item.getName(), item); + } + + void addAdditionalParam(String key, String value) { + additionalParams.put(key, value); + } + + void exportSettings(File file) throws JSONException, IOException { + JSONObject json = createItemsJson(); + OutputStream os = new BufferedOutputStream(new FileOutputStream(file), SettingsHelper.BUFFER); + ZipOutputStream zos = new ZipOutputStream(os); + try { + ZipEntry entry = new ZipEntry("items.json"); + zos.putNextEntry(entry); + zos.write(json.toString(2).getBytes("UTF-8")); + zos.closeEntry(); + if (exportItemsFiles) { + writeItemFiles(zos); + } + zos.flush(); + zos.finish(); + } finally { + Algorithms.closeStream(zos); + Algorithms.closeStream(os); + } + } + + private void writeItemFiles(ZipOutputStream zos) throws IOException { + for (SettingsItem item : items.values()) { + SettingsItemWriter writer = item.getWriter(); + if (writer != null) { + String fileName = item.getFileName(); + if (Algorithms.isEmpty(fileName)) { + fileName = item.getDefaultFileName(); + } + ZipEntry entry = new ZipEntry(fileName); + zos.putNextEntry(entry); + writer.writeToStream(zos); + zos.closeEntry(); + } + } + } + + private JSONObject createItemsJson() throws JSONException { + JSONObject json = new JSONObject(); + json.put("version", SettingsHelper.VERSION); + for (Map.Entry param : additionalParams.entrySet()) { + json.put(param.getKey(), param.getValue()); + } + JSONArray itemsJson = new JSONArray(); + for (SettingsItem item : items.values()) { + itemsJson.put(new JSONObject(item.toJson())); + } + json.put("items", itemsJson); + return json; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java new file mode 100644 index 0000000000..1c53ad987b --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -0,0 +1,614 @@ +package net.osmand.plus.settings.backend.backup; + +import android.annotation.SuppressLint; +import android.os.AsyncTask; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.AndroidUtils; +import net.osmand.IndexConstants; +import net.osmand.PlatformUtil; +import net.osmand.data.LatLon; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.map.TileSourceManager.TileSourceTemplate; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; +import net.osmand.plus.poi.PoiUIFilter; +import net.osmand.plus.quickaction.QuickAction; +import net.osmand.plus.quickaction.QuickActionRegistry; +import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; +import net.osmand.plus.settings.backend.ExportSettingsType; + +import org.apache.commons.logging.Log; +import org.json.JSONException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; + +/* + Usage: + + SettingsHelper helper = app.getSettingsHelper(); + File file = new File(app.getAppPath(null), "settings.zip"); + + List items = new ArrayList<>(); + items.add(new GlobalSettingsItem(app.getSettings())); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.DEFAULT)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.CAR)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.PEDESTRIAN)); + items.add(new ProfileSettingsItem(app.getSettings(), ApplicationMode.BICYCLE)); + items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 2.gpx"))); + items.add(new FileSettingsItem(app, new File(app.getAppPath(GPX_INDEX_DIR), "Day 3.gpx"))); + items.add(new FileSettingsItem(app, new File(app.getAppPath(RENDERERS_DIR), "default.render.xml"))); + items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '1'}, "data1")); + items.add(new DataSettingsItem(new byte[] {'t', 'e', 's', 't', '2'}, "data2")); + + helper.exportSettings(file, items); + + helper.importSettings(file); + */ + +public class SettingsHelper { + + public static final int VERSION = 1; + + public static final String SETTINGS_TYPE_LIST_KEY = "settings_type_list_key"; + public static final String REPLACE_KEY = "replace"; + public static final String SETTINGS_LATEST_CHANGES_KEY = "settings_latest_changes"; + public static final String SETTINGS_VERSION_KEY = "settings_version"; + + public static final int BUFFER = 1024; + + protected static final Log LOG = PlatformUtil.getLog(SettingsHelper.class); + + private OsmandApplication app; + + private ImportAsyncTask importTask; + private Map exportAsyncTasks = new HashMap<>(); + + public interface SettingsImportListener { + void onSettingsImportFinished(boolean succeed, @NonNull List items); + } + + public interface SettingsCollectListener { + void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items); + } + + public interface CheckDuplicatesListener { + void onDuplicatesChecked(@NonNull List duplicates, List items); + } + + public interface SettingsExportListener { + void onSettingsExportFinished(@NonNull File file, boolean succeed); + } + + public SettingsHelper(@NonNull OsmandApplication app) { + this.app = app; + } + + public enum SettingsItemType { + GLOBAL, + PROFILE, + PLUGIN, + DATA, + FILE, + RESOURCES, + QUICK_ACTIONS, + POI_UI_FILTERS, + MAP_SOURCES, + AVOID_ROADS, + SUGGESTED_DOWNLOADS, + DOWNLOADS + } + + public static Map> getSettingsToOperate(List settingsItems, boolean importComplete) { + Map> settingsToOperate = new HashMap<>(); + List profiles = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List routingFilesList = new ArrayList<>(); + List renderFilesList = new ArrayList<>(); + List avoidRoads = new ArrayList<>(); + for (SettingsItem item : settingsItems) { + switch (item.getType()) { + case PROFILE: + profiles.add(((ProfileSettingsItem) item).getModeBean()); + break; + case FILE: + FileSettingsItem fileItem = (FileSettingsItem) item; + if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + renderFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + routingFilesList.add(fileItem.getFile()); + } + break; + case QUICK_ACTIONS: + QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; + if (importComplete) { + quickActions.addAll(quickActionsItem.getAppliedItems()); + } else { + quickActions.addAll(quickActionsItem.getItems()); + } + break; + case POI_UI_FILTERS: + PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; + if (importComplete) { + poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); + } else { + poiUIFilters.addAll(poiUiFilterItem.getItems()); + } + break; + case MAP_SOURCES: + MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; + if (importComplete) { + tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); + } else { + tileSourceTemplates.addAll(mapSourcesItem.getItems()); + } + break; + case AVOID_ROADS: + AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; + if (importComplete) { + avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); + } else { + avoidRoads.addAll(avoidRoadsItem.getItems()); + } + break; + default: + break; + } + } + + if (!profiles.isEmpty()) { + settingsToOperate.put(ExportSettingsType.PROFILE, profiles); + } + if (!quickActions.isEmpty()) { + settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); + } + if (!poiUIFilters.isEmpty()) { + settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); + } + if (!tileSourceTemplates.isEmpty()) { + settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); + } + if (!renderFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); + } + if (!routingFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); + } + if (!avoidRoads.isEmpty()) { + settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); + } + return settingsToOperate; + } + + @SuppressLint("StaticFieldLeak") + public class ImportAsyncTask extends AsyncTask> { + + private File file; + private String latestChanges; + private int version; + + private SettingsImportListener importListener; + private SettingsCollectListener collectListener; + private CheckDuplicatesListener duplicatesListener; + private SettingsImporter importer; + + private List items = new ArrayList<>(); + private List selectedItems = new ArrayList<>(); + private List duplicates; + + private ImportType importType; + private boolean importDone; + + ImportAsyncTask(@NonNull File file, String latestChanges, int version, @Nullable SettingsCollectListener collectListener) { + this.file = file; + this.collectListener = collectListener; + this.latestChanges = latestChanges; + this.version = version; + importer = new SettingsImporter(app); + importType = ImportType.COLLECT; + } + + ImportAsyncTask(@NonNull File file, @NonNull List items, String latestChanges, int version, @Nullable SettingsImportListener importListener) { + this.file = file; + this.importListener = importListener; + this.items = items; + this.latestChanges = latestChanges; + this.version = version; + importer = new SettingsImporter(app); + importType = ImportType.IMPORT; + } + + ImportAsyncTask(@NonNull File file, @NonNull List items, @NonNull List selectedItems, @Nullable CheckDuplicatesListener duplicatesListener) { + this.file = file; + this.items = items; + this.duplicatesListener = duplicatesListener; + this.selectedItems = selectedItems; + importer = new SettingsImporter(app); + importType = ImportType.CHECK_DUPLICATES; + } + + @Override + protected void onPreExecute() { + ImportAsyncTask importTask = SettingsHelper.this.importTask; + if (importTask != null && !importTask.importDone) { + finishImport(importListener, false, items); + } + SettingsHelper.this.importTask = this; + } + + @Override + protected List doInBackground(Void... voids) { + switch (importType) { + case COLLECT: + 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); + } + break; + case CHECK_DUPLICATES: + this.duplicates = getDuplicatesData(selectedItems); + return selectedItems; + case IMPORT: + return items; + } + return null; + } + + @Override + protected void onPostExecute(@Nullable List items) { + if (items != null && importType != ImportType.CHECK_DUPLICATES) { + this.items = items; + } else { + selectedItems = items; + } + switch (importType) { + case COLLECT: + importDone = true; + collectListener.onSettingsCollectFinished(true, false, this.items); + break; + case CHECK_DUPLICATES: + importDone = true; + if (duplicatesListener != null) { + duplicatesListener.onDuplicatesChecked(duplicates, selectedItems); + } + break; + case IMPORT: + if (items != null && items.size() > 0) { + for (SettingsItem item : items) { + item.apply(); + } + new ImportItemsAsyncTask(file, importListener, items).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + break; + } + } + + public List getItems() { + return items; + } + + public File getFile() { + return file; + } + + public void setImportListener(SettingsImportListener importListener) { + this.importListener = importListener; + } + + public void setDuplicatesListener(CheckDuplicatesListener duplicatesListener) { + this.duplicatesListener = duplicatesListener; + } + + ImportType getImportType() { + return importType; + } + + boolean isImportDone() { + return importDone; + } + + public List getDuplicates() { + return duplicates; + } + + public List getSelectedItems() { + return selectedItems; + } + + private List getDuplicatesData(List items) { + List duplicateItems = new ArrayList<>(); + for (SettingsItem item : items) { + if (item instanceof ProfileSettingsItem) { + if (item.exists()) { + duplicateItems.add(((ProfileSettingsItem) item).getModeBean()); + } + } else if (item instanceof CollectionSettingsItem) { + List duplicates = ((CollectionSettingsItem) item).processDuplicateItems(); + if (!duplicates.isEmpty()) { + duplicateItems.addAll(duplicates); + } + } else if (item instanceof FileSettingsItem) { + if (item.exists()) { + duplicateItems.add(((FileSettingsItem) item).getFile()); + } + } + } + return duplicateItems; + } + } + + @Nullable + public ImportAsyncTask getImportTask() { + return importTask; + } + + @Nullable + public ImportType getImportTaskType() { + ImportAsyncTask importTask = this.importTask; + return importTask != null ? importTask.getImportType() : null; + } + + public boolean isImportDone() { + ImportAsyncTask importTask = this.importTask; + return importTask == null || importTask.isImportDone(); + } + + public boolean isFileExporting(File file) { + return exportAsyncTasks.containsKey(file); + } + + public void updateExportListener(File file, SettingsExportListener listener) { + ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); + if (exportAsyncTask != null) { + exportAsyncTask.listener = listener; + } + } + + @SuppressLint("StaticFieldLeak") + private class ImportItemsAsyncTask extends AsyncTask { + + private SettingsImporter importer; + private File file; + private SettingsImportListener listener; + private List items; + + ImportItemsAsyncTask(@NonNull File file, + @Nullable SettingsImportListener listener, + @NonNull List items) { + importer = new SettingsImporter(app); + this.file = file; + this.listener = listener; + this.items = items; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + importer.importItems(file, items); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } + return false; + } + + @Override + protected void onPostExecute(Boolean success) { + finishImport(listener, success, items); + } + } + + private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { + importTask = null; + List warnings = new ArrayList<>(); + for (SettingsItem item : items) { + warnings.addAll(item.getWarnings()); + } + if (!warnings.isEmpty()) { + app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); + } + if (listener != null) { + listener.onSettingsImportFinished(success, items); + } + } + + @SuppressLint("StaticFieldLeak") + private class ExportAsyncTask extends AsyncTask { + + private SettingsExporter exporter; + private File file; + private SettingsExportListener listener; + + ExportAsyncTask(@NonNull File settingsFile, + @Nullable SettingsExportListener listener, + @NonNull List items, boolean exportItemsFiles) { + this.file = settingsFile; + this.listener = listener; + this.exporter = new SettingsExporter(exportItemsFiles); + for (SettingsItem item : items) { + exporter.addSettingsItem(item); + } + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + exporter.exportSettings(file); + return true; + } catch (JSONException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } + return false; + } + + @Override + protected void onPostExecute(Boolean success) { + exportAsyncTasks.remove(file); + if (listener != null) { + listener.onSettingsExportFinished(file, success); + } + } + } + + public void collectSettings(@NonNull File settingsFile, String latestChanges, int version, + @Nullable SettingsCollectListener listener) { + new ImportAsyncTask(settingsFile, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void checkDuplicates(@NonNull File file, @NonNull List items, @NonNull List selectedItems, CheckDuplicatesListener listener) { + new ImportAsyncTask(file, items, selectedItems, 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, boolean exportItemsFiles) { + File file = new File(fileDir, fileName + OSMAND_SETTINGS_FILE_EXT); + ExportAsyncTask exportAsyncTask = new ExportAsyncTask(file, listener, items, exportItemsFiles); + exportAsyncTasks.put(file, exportAsyncTask); + exportAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void exportSettings(@NonNull File fileDir, @NonNull String fileName, @Nullable SettingsExportListener listener, + boolean exportItemsFiles, @NonNull SettingsItem... items) { + exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items)), exportItemsFiles); + } + + public enum ImportType { + COLLECT, + CHECK_DUPLICATES, + IMPORT + } + + public List getFilteredSettingsItems(Map> additionalData, + List settingsTypes) { + List settingsItems = new ArrayList<>(); + for (ExportSettingsType settingsType : settingsTypes) { + List settingsDataObjects = additionalData.get(settingsType); + if (settingsDataObjects != null) { + for (Object object : settingsDataObjects) { + if (object instanceof ApplicationModeBean) { + settingsItems.add(new ProfileSettingsItem(app, null, (ApplicationModeBean) object)); + } + } + settingsItems.addAll(prepareAdditionalSettingsItems(new ArrayList<>(settingsDataObjects))); + } + } + return settingsItems; + } + + public Map> getAdditionalData() { + Map> dataList = new HashMap<>(); + + QuickActionRegistry registry = app.getQuickActionRegistry(); + List actionsList = registry.getQuickActions(); + if (!actionsList.isEmpty()) { + dataList.put(ExportSettingsType.QUICK_ACTIONS, actionsList); + } + + List poiList = app.getPoiFilters().getUserDefinedPoiFilters(false); + if (!poiList.isEmpty()) { + dataList.put(ExportSettingsType.POI_TYPES, poiList); + } + + List iTileSources = new ArrayList<>(); + Set tileSourceNames = app.getSettings().getTileSourceEntries(true).keySet(); + for (String name : tileSourceNames) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + name); + 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.getUrlTemplate() != null) { + iTileSources.add(template); + } + } + } + if (!iTileSources.isEmpty()) { + dataList.put(ExportSettingsType.MAP_SOURCES, iTileSources); + } + + Map externalRenderers = app.getRendererRegistry().getExternalRenderers(); + if (!externalRenderers.isEmpty()) { + dataList.put(ExportSettingsType.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.put(ExportSettingsType.CUSTOM_ROUTING, Arrays.asList(fl)); + } + } + + Map impassableRoads = app.getAvoidSpecificRoads().getImpassableRoads(); + if (!impassableRoads.isEmpty()) { + dataList.put(ExportSettingsType.AVOID_ROADS, new ArrayList<>(impassableRoads.values())); + } + return dataList; + } + + public List prepareAdditionalSettingsItems(List data) { + List settingsItems = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List avoidRoads = new ArrayList<>(); + for (Object object : data) { + if (object instanceof QuickAction) { + quickActions.add((QuickAction) object); + } else if (object instanceof PoiUIFilter) { + poiUIFilters.add((PoiUIFilter) object); + } else if (object instanceof TileSourceTemplate || object instanceof SQLiteTileSource) { + tileSourceTemplates.add((ITileSource) object); + } else if (object instanceof File) { + try { + settingsItems.add(new FileSettingsItem(app, (File) object)); + } catch (IllegalArgumentException e) { + LOG.warn("Trying to export unsuported file type", e); + } + } else if (object instanceof AvoidRoadInfo) { + avoidRoads.add((AvoidRoadInfo) object); + } + } + if (!quickActions.isEmpty()) { + settingsItems.add(new QuickActionsSettingsItem(app, quickActions)); + } + if (!poiUIFilters.isEmpty()) { + settingsItems.add(new PoiUiFiltersSettingsItem(app, poiUIFilters)); + } + if (!tileSourceTemplates.isEmpty()) { + settingsItems.add(new MapSourcesSettingsItem(app, tileSourceTemplates)); + } + if (!avoidRoads.isEmpty()) { + settingsItems.add(new AvoidRoadsSettingsItem(app, avoidRoads)); + } + return settingsItems; + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java new file mode 100644 index 0000000000..a06b8e8749 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsImporter.java @@ -0,0 +1,137 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static net.osmand.IndexConstants.OSMAND_SETTINGS_FILE_EXT; + +class SettingsImporter { + + private OsmandApplication app; + + SettingsImporter(@NonNull OsmandApplication app) { + this.app = app; + } + + List collectItems(@NonNull File file) throws IllegalArgumentException, IOException { + return processItems(file, null); + } + + void importItems(@NonNull File file, @NonNull List items) throws IllegalArgumentException, IOException { + processItems(file, items); + } + + private List getItemsFromJson(@NonNull File file) throws IOException { + List items = new ArrayList<>(); + ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); + InputStream ois = new BufferedInputStream(zis); + try { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String fileName = checkEntryName(entry.getName()); + if (fileName.equals("items.json")) { + String itemsJson = null; + try { + itemsJson = Algorithms.readFromInputStream(ois, false).toString(); + } catch (IOException e) { + SettingsHelper.LOG.error("Error reading items.json: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } finally { + zis.closeEntry(); + } + try { + SettingsItemsFactory itemsFactory = new SettingsItemsFactory(app, itemsJson); + items.addAll(itemsFactory.getItems()); + } catch (IllegalArgumentException e) { + SettingsHelper.LOG.error("Error parsing items: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } catch (JSONException e) { + SettingsHelper.LOG.error("Error parsing items: " + itemsJson, e); + throw new IllegalArgumentException("No items"); + } + break; + } + } + } catch (IOException ex) { + SettingsHelper.LOG.error("Failed to read next entry", ex); + } finally { + Algorithms.closeStream(ois); + Algorithms.closeStream(zis); + } + return items; + } + + private List processItems(@NonNull File file, @Nullable List items) throws IllegalArgumentException, IOException { + boolean collecting = items == null; + if (collecting) { + items = getItemsFromJson(file); + } else { + if (items.size() == 0) { + throw new IllegalArgumentException("No items"); + } + } + ZipInputStream zis = new ZipInputStream(new FileInputStream(file)); + InputStream ois = new BufferedInputStream(zis); + try { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String fileName = checkEntryName(entry.getName()); + SettingsItem item = null; + for (SettingsItem settingsItem : items) { + if (settingsItem != null && settingsItem.applyFileName(fileName)) { + item = settingsItem; + break; + } + } + if (item != null && collecting && item.shouldReadOnCollecting() + || item != null && !collecting && !item.shouldReadOnCollecting()) { + try { + SettingsItemReader reader = item.getReader(); + if (reader != null) { + reader.readFromStream(ois); + } + } catch (IllegalArgumentException e) { + item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); + SettingsHelper.LOG.error("Error reading item data: " + item.getName(), e); + } catch (IOException e) { + item.warnings.add(app.getString(R.string.settings_item_read_error, item.getName())); + SettingsHelper.LOG.error("Error reading item data: " + item.getName(), e); + } finally { + zis.closeEntry(); + } + } + } + } catch (IOException ex) { + SettingsHelper.LOG.error("Failed to read next entry", ex); + } finally { + Algorithms.closeStream(ois); + Algorithms.closeStream(zis); + } + return items; + } + + private String checkEntryName(String entryName) { + String fileExt = OSMAND_SETTINGS_FILE_EXT + "/"; + int index = entryName.indexOf(fileExt); + if (index != -1) { + entryName = entryName.substring(index + fileExt.length()); + } + return entryName; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java new file mode 100644 index 0000000000..3bc41091e2 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java @@ -0,0 +1,236 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.util.Algorithms; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public abstract class SettingsItem { + + protected OsmandApplication app; + + protected String pluginId; + protected String fileName; + + boolean shouldReplace = false; + + protected List warnings; + + SettingsItem(@NonNull OsmandApplication app) { + this.app = app; + init(); + } + + SettingsItem(@NonNull OsmandApplication app, @Nullable SettingsItem baseItem) { + this.app = app; + if (baseItem != null) { + this.pluginId = baseItem.pluginId; + this.fileName = baseItem.fileName; + } + init(); + } + + SettingsItem(OsmandApplication app, @NonNull JSONObject json) throws JSONException { + this.app = app; + init(); + readFromJson(json); + } + + protected void init() { + warnings = new ArrayList<>(); + } + + public List getWarnings() { + return warnings; + } + + @NonNull + public abstract SettingsHelper.SettingsItemType getType(); + + @NonNull + public abstract String getName(); + + @NonNull + public abstract String getPublicName(@NonNull Context ctx); + + @NonNull + public String getDefaultFileName() { + return getName() + getDefaultFileExtension(); + } + + @NonNull + public String getDefaultFileExtension() { + return ".json"; + } + + public String getPluginId() { + return pluginId; + } + + @Nullable + public String getFileName() { + return fileName; + } + + public boolean applyFileName(@NonNull String fileName) { + String n = getFileName(); + return n != null && n.endsWith(fileName); + } + + public boolean shouldReadOnCollecting() { + return false; + } + + public void setShouldReplace(boolean shouldReplace) { + this.shouldReplace = shouldReplace; + } + + static SettingsHelper.SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { + String type = json.has("type") ? json.getString("type") : null; + if (type == null) { + throw new IllegalArgumentException("No type field"); + } + if (type.equals("QUICK_ACTION")) { + type = "QUICK_ACTIONS"; + } + return SettingsHelper.SettingsItemType.valueOf(type); + } + + public boolean exists() { + return false; + } + + public void apply() { + // non implemented + } + + void readFromJson(@NonNull JSONObject json) throws JSONException { + pluginId = json.has("pluginId") ? json.getString("pluginId") : null; + if (json.has("name")) { + fileName = json.getString("name") + getDefaultFileExtension(); + } + if (json.has("file")) { + fileName = json.getString("file"); + } + readItemsFromJson(json); + } + + void writeToJson(@NonNull JSONObject json) throws JSONException { + json.put("type", getType().name()); + String pluginId = getPluginId(); + if (!Algorithms.isEmpty(pluginId)) { + json.put("pluginId", pluginId); + } + if (getWriter() != null) { + String fileName = getFileName(); + if (Algorithms.isEmpty(fileName)) { + fileName = getDefaultFileName(); + } + json.put("file", fileName); + } + writeItemsToJson(json); + } + + String toJson() throws JSONException { + JSONObject json = new JSONObject(); + writeToJson(json); + return json.toString(); + } + + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + // override + } + + void writeItemsToJson(@NonNull JSONObject json) { + // override + } + + @Nullable + abstract SettingsItemReader getReader(); + + @Nullable + abstract SettingsItemWriter getWriter(); + + @NonNull + SettingsItemReader getJsonReader() { + return new SettingsItemReader(this) { + @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 json = buf.toString(); + if (json.length() == 0) { + throw new IllegalArgumentException("Json body is empty"); + } + try { + readItemsFromJson(new JSONObject(json)); + } catch (JSONException e) { + throw new IllegalArgumentException("Json parsing error", e); + } + } + }; + } + + @NonNull + SettingsItemWriter getJsonWriter() { + return new SettingsItemWriter(this) { + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + JSONObject json = new JSONObject(); + writeItemsToJson(json); + if (json.length() > 0) { + try { + String s = json.toString(2); + outputStream.write(s.getBytes("UTF-8")); + } catch (JSONException e) { + SettingsHelper.LOG.error("Failed to write json to stream", e); + } + return true; + } + return false; + } + }; + } + + @Override + public int hashCode() { + return (getType().name() + getName()).hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof SettingsItem)) { + return false; + } + + SettingsItem item = (SettingsItem) other; + return item.getType() == getType() + && item.getName().equals(getName()) + && Algorithms.stringsEqual(item.getFileName(), getFileName()); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java new file mode 100644 index 0000000000..daa66f979a --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemReader.java @@ -0,0 +1,17 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +public abstract class SettingsItemReader { + + private T item; + + public SettingsItemReader(@NonNull T item) { + this.item = item; + } + + public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException; +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java new file mode 100644 index 0000000000..9e3cf61377 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemWriter.java @@ -0,0 +1,21 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +public abstract class SettingsItemWriter { + + private T item; + + public SettingsItemWriter(T item) { + this.item = item; + } + + public T getItem() { + return item; + } + + public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException; +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java new file mode 100644 index 0000000000..78c90b99ad --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java @@ -0,0 +1,131 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class SettingsItemsFactory { + + private OsmandApplication app; + private List items = new ArrayList<>(); + + SettingsItemsFactory(@NonNull OsmandApplication app, String jsonStr) throws IllegalArgumentException, JSONException { + this.app = app; + collectItems(new JSONObject(jsonStr)); + } + + private void collectItems(JSONObject json) throws IllegalArgumentException, JSONException { + JSONArray itemsJson = json.getJSONArray("items"); + int version = json.has("version") ? json.getInt("version") : 1; + if (version > SettingsHelper.VERSION) { + throw new IllegalArgumentException("Unsupported osf version: " + version); + } + Map> pluginItems = new HashMap<>(); + for (int i = 0; i < itemsJson.length(); i++) { + JSONObject itemJson = itemsJson.getJSONObject(i); + SettingsItem item; + try { + item = createItem(itemJson); + items.add(item); + String pluginId = item.getPluginId(); + if (pluginId != null && item.getType() != SettingsHelper.SettingsItemType.PLUGIN) { + List items = pluginItems.get(pluginId); + if (items != null) { + items.add(item); + } else { + items = new ArrayList<>(); + items.add(item); + pluginItems.put(pluginId, items); + } + } + } catch (IllegalArgumentException e) { + SettingsHelper.LOG.error("Error creating item from json: " + itemJson, e); + } + } + if (items.size() == 0) { + throw new IllegalArgumentException("No items"); + } + for (SettingsItem item : items) { + if (item instanceof PluginSettingsItem) { + PluginSettingsItem pluginSettingsItem = ((PluginSettingsItem) item); + List pluginDependentItems = pluginItems.get(pluginSettingsItem.getName()); + if (!Algorithms.isEmpty(pluginDependentItems)) { + pluginSettingsItem.getPluginDependentItems().addAll(pluginDependentItems); + } + } + } + } + + @NonNull + public List getItems() { + return items; + } + + @Nullable + public SettingsItem getItemByFileName(@NonNull String fileName) { + for (SettingsItem item : items) { + if (Algorithms.stringsEqual(item.getFileName(), fileName)) { + return item; + } + } + return null; + } + + @NonNull + private SettingsItem createItem(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { + SettingsItem item = null; + SettingsHelper.SettingsItemType type = SettingsItem.parseItemType(json); + OsmandSettings settings = app.getSettings(); + switch (type) { + case GLOBAL: + item = new GlobalSettingsItem(settings); + break; + case PROFILE: + item = new ProfileSettingsItem(app, json); + break; + case PLUGIN: + item = new PluginSettingsItem(app, json); + break; + case DATA: + item = new DataSettingsItem(app, json); + break; + case FILE: + item = new FileSettingsItem(app, json); + break; + case RESOURCES: + item = new ResourcesSettingsItem(app, json); + break; + case QUICK_ACTIONS: + item = new QuickActionsSettingsItem(app, json); + break; + case POI_UI_FILTERS: + item = new PoiUiFiltersSettingsItem(app, json); + break; + case MAP_SOURCES: + item = new MapSourcesSettingsItem(app, json); + break; + case AVOID_ROADS: + item = new AvoidRoadsSettingsItem(app, json); + break; + case SUGGESTED_DOWNLOADS: + item = new SuggestedDownloadsItem(app, json); + break; + case DOWNLOADS: + item = new DownloadsItem(app, json); + break; + } + return item; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java new file mode 100644 index 0000000000..ae683f49b8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItem.java @@ -0,0 +1,76 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.InputStream; + +public abstract class StreamSettingsItem extends SettingsItem { + + @Nullable + private InputStream inputStream; + protected String name; + + public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull String name) { + super(app); + this.name = name; + this.fileName = name; + } + + StreamSettingsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + public StreamSettingsItem(@NonNull OsmandApplication app, @NonNull InputStream inputStream, @NonNull String name) { + super(app); + this.inputStream = inputStream; + this.name = name; + this.fileName = name; + } + + @Nullable + public InputStream getInputStream() { + return inputStream; + } + + protected void setInputStream(@Nullable InputStream inputStream) { + this.inputStream = inputStream; + } + + @NonNull + @Override + public String getName() { + return name; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return getName(); + } + + @NonNull + @Override + public String getDefaultFileExtension() { + return ""; + } + + @Override + void readFromJson(@NonNull JSONObject json) throws JSONException { + super.readFromJson(json); + name = json.has("name") ? json.getString("name") : null; + } + + @Nullable + @Override + public SettingsItemWriter getWriter() { + return new StreamSettingsItemWriter(this); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java new file mode 100644 index 0000000000..b231f1f8ae --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemReader.java @@ -0,0 +1,10 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +public abstract class StreamSettingsItemReader extends SettingsItemReader { + + public StreamSettingsItemReader(@NonNull StreamSettingsItem item) { + super(item); + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java new file mode 100644 index 0000000000..0029568a93 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/StreamSettingsItemWriter.java @@ -0,0 +1,34 @@ +package net.osmand.plus.settings.backend.backup; + +import androidx.annotation.NonNull; + +import net.osmand.util.Algorithms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class StreamSettingsItemWriter extends SettingsItemWriter { + + public StreamSettingsItemWriter(StreamSettingsItem item) { + super(item); + } + + @Override + public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException { + boolean hasData = false; + InputStream is = getItem().getInputStream(); + if (is != null) { + byte[] data = new byte[SettingsHelper.BUFFER]; + int count; + while ((count = is.read(data, 0, SettingsHelper.BUFFER)) != -1) { + outputStream.write(data, 0, count); + if (!hasData) { + hasData = true; + } + } + Algorithms.closeStream(is); + } + return hasData; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java new file mode 100644 index 0000000000..2ff0f88371 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java @@ -0,0 +1,128 @@ +package net.osmand.plus.settings.backend.backup; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.CustomOsmandPlugin; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class SuggestedDownloadsItem extends SettingsItem { + + private List items; + + SuggestedDownloadsItem(@NonNull OsmandApplication app, @NonNull JSONObject json) throws JSONException { + super(app, json); + } + + @Override + protected void init() { + super.init(); + items = new ArrayList<>(); + } + + @NonNull + @Override + public SettingsHelper.SettingsItemType getType() { + return SettingsHelper.SettingsItemType.SUGGESTED_DOWNLOADS; + + } + + @NonNull + @Override + public String getName() { + return "suggested_downloads"; + } + + @NonNull + @Override + public String getPublicName(@NonNull Context ctx) { + return "suggested_downloads"; + } + + public List getItems() { + return items; + } + + @Override + void readItemsFromJson(@NonNull JSONObject json) throws IllegalArgumentException { + try { + if (!json.has("items")) { + return; + } + JSONArray jsonArray = json.getJSONArray("items"); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + String scopeId = object.optString("scope-id"); + String searchType = object.optString("search-type"); + int limit = object.optInt("limit", -1); + + List names = new ArrayList<>(); + if (object.has("names")) { + JSONArray namesArray = object.getJSONArray("names"); + for (int j = 0; j < namesArray.length(); j++) { + names.add(namesArray.getString(j)); + } + } + CustomOsmandPlugin.SuggestedDownloadItem suggestedDownload = new CustomOsmandPlugin.SuggestedDownloadItem(scopeId, searchType, names, limit); + items.add(suggestedDownload); + } + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_read_error, String.valueOf(getType()))); + throw new IllegalArgumentException("Json parse error", e); + } + } + + @Override + void writeItemsToJson(@NonNull JSONObject json) { + JSONArray jsonArray = new JSONArray(); + if (!items.isEmpty()) { + try { + for (CustomOsmandPlugin.SuggestedDownloadItem downloadItem : items) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("scope-id", downloadItem.getScopeId()); + if (downloadItem.getLimit() != -1) { + jsonObject.put("limit", downloadItem.getLimit()); + } + if (!Algorithms.isEmpty(downloadItem.getSearchType())) { + jsonObject.put("search-type", downloadItem.getSearchType()); + } + if (!Algorithms.isEmpty(downloadItem.getNames())) { + JSONArray namesArray = new JSONArray(); + for (String downloadName : downloadItem.getNames()) { + namesArray.put(downloadName); + } + jsonObject.put("names", namesArray); + } + jsonArray.put(jsonObject); + } + json.put("items", jsonArray); + } catch (JSONException e) { + warnings.add(app.getString(R.string.settings_item_write_error, String.valueOf(getType()))); + SettingsHelper.LOG.error("Failed write to json", e); + } + } + } + + @Nullable + @Override + SettingsItemReader getReader() { + return null; + } + + @Nullable + @Override + SettingsItemWriter getWriter() { + return null; + } +} diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java index e65a351041..0f7dae8add 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureProfileFragment.java @@ -35,8 +35,8 @@ import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsCollectListener; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsCollectListener; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; @@ -44,6 +44,7 @@ import net.osmand.plus.helpers.FontCache; import net.osmand.plus.openseamapsplugin.NauticalMapsPlugin; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; import net.osmand.plus.skimapsplugin.SkiMapsPlugin; @@ -186,9 +187,9 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co private void restoreCustomModeFromFile(final File file) { app.getSettingsHelper().collectSettings(file, "", 1, new SettingsCollectListener() { @Override - public void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items) { + public void onSettingsCollectFinished(boolean succeed, boolean empty, @NonNull List items) { if (succeed) { - for (SettingsHelper.SettingsItem item : items) { + for (SettingsItem item : items) { item.setShouldReplace(true); } importBackupSettingsItems(file, items); @@ -197,10 +198,10 @@ public class ConfigureProfileFragment extends BaseSettingsFragment implements Co }); } - private void importBackupSettingsItems(File file, List items) { + private void importBackupSettingsItems(File file, List items) { app.getSettingsHelper().importSettings(file, items, "", 1, new SettingsHelper.SettingsImportListener() { @Override - public void onSettingsImportFinished(boolean succeed, @NonNull List items) { + public void onSettingsImportFinished(boolean succeed, @NonNull List items) { app.showToastMessage(R.string.profile_prefs_reset_successful); updateCopiedOrResetPrefs(); } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportProfileBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportProfileBottomSheet.java index c95f7d578c..2376ca6158 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ExportProfileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ExportProfileBottomSheet.java @@ -33,9 +33,9 @@ import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; import net.osmand.plus.settings.backend.ExportSettingsType; import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.settings.bottomsheets.BasePreferenceBottomSheet; import org.apache.commons.logging.Log; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java index a8e7acc8e3..27278775f0 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportCompleteFragment.java @@ -31,8 +31,8 @@ import net.osmand.plus.quickaction.QuickActionListFragment; import net.osmand.plus.routepreparationmenu.AvoidRoadsBottomSheetDialogFragment; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ExportSettingsType; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsItem; import java.util.List; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java index 7ea985e19a..53aa767df3 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportDuplicatesFragment.java @@ -29,10 +29,10 @@ import net.osmand.plus.AppInitializer; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.ImportAsyncTask; -import net.osmand.plus.settings.backend.SettingsHelper.ImportType; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportAsyncTask; +import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportType; +import net.osmand.plus.settings.backend.backup.SettingsItem; import net.osmand.plus.UiUtilities; import net.osmand.plus.base.BaseOsmAndFragment; import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java index dd02f3f716..8386f50f73 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java @@ -42,17 +42,17 @@ import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.quickaction.QuickAction; import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; import net.osmand.plus.settings.backend.ExportSettingsType; -import net.osmand.plus.settings.backend.SettingsHelper; -import net.osmand.plus.settings.backend.SettingsHelper.AvoidRoadsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.FileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ImportAsyncTask; -import net.osmand.plus.settings.backend.SettingsHelper.ImportType; -import net.osmand.plus.settings.backend.SettingsHelper.MapSourcesSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.PoiUiFiltersSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.ProfileSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.QuickActionsSettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsHelper; +import net.osmand.plus.settings.backend.backup.AvoidRoadsSettingsItem; +import net.osmand.plus.settings.backend.backup.FileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportAsyncTask; +import net.osmand.plus.settings.backend.backup.SettingsHelper.ImportType; +import net.osmand.plus.settings.backend.backup.MapSourcesSettingsItem; +import net.osmand.plus.settings.backend.backup.PoiUiFiltersSettingsItem; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsItemType; import net.osmand.plus.widgets.TextViewEx; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java index c3381520a7..3820fd72b8 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java @@ -18,8 +18,8 @@ import net.osmand.CallbackWithObject; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItem; -import net.osmand.plus.settings.backend.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsItemType; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index 6fcf8a17bf..1bdbc38b19 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -42,7 +42,8 @@ import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.R; -import net.osmand.plus.settings.backend.SettingsHelper; +import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; +import net.osmand.plus.settings.backend.backup.SettingsHelper; import net.osmand.plus.UiUtilities; import net.osmand.plus.UiUtilities.DialogButtonType; import net.osmand.plus.profiles.LocationIcon; @@ -828,7 +829,7 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment { tempDir.mkdirs(); } app.getSettingsHelper().exportSettings(tempDir, mode.getStringKey(), - getSettingsExportListener(), true, new SettingsHelper.ProfileSettingsItem(app, mode)); + getSettingsExportListener(), true, new ProfileSettingsItem(app, mode)); } } From 07ea6b424fa434221c76559ba2bf548b45c2a625 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Wed, 14 Oct 2020 23:04:56 +0300 Subject: [PATCH 069/123] Fix get item by position --- .../editors/PointEditorFragmentNew.java | 2 +- .../other/HorizontalSelectionAdapter.java | 13 ++++++++----- .../bottomsheets/VehicleParametersBottomSheet.java | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java index 0bc215c366..c487b7acc6 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/editors/PointEditorFragmentNew.java @@ -632,7 +632,7 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment { iconCategoriesRecyclerView.setAdapter(horizontalSelectionAdapter); iconCategoriesRecyclerView.setLayoutManager(new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false)); horizontalSelectionAdapter.notifyDataSetChanged(); - iconCategoriesRecyclerView.smoothScrollToPosition(horizontalSelectionAdapter.getItemPosition(selectedIconCategory)); + iconCategoriesRecyclerView.smoothScrollToPosition(horizontalSelectionAdapter.getItemPositionByTitle(selectedIconCategory)); for (String name : iconNameList) { selectIcon.addView(createIconItemView(name, selectIcon), new FlowLayout.LayoutParams(0, 0)); } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java index 0eb3c3b21e..06783d6f50 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java @@ -110,13 +110,16 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter= 0) { recyclerView.smoothScrollToPosition(itemPosition); } From 7eb1f2020a207f46d44f574ceb25a166f5d34108 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Thu, 15 Oct 2020 00:21:41 +0300 Subject: [PATCH 070/123] Move SettingsItemType to separate file and add new types --- .../settings/backend/ExportSettingsType.java | 7 +- .../backup/AvoidRoadsSettingsItem.java | 4 +- .../backend/backup/DataSettingsItem.java | 4 +- .../backend/backup/DownloadsItem.java | 4 +- .../backend/backup/FileSettingsItem.java | 4 +- .../backend/backup/GlobalSettingsItem.java | 4 +- .../backup/MapSourcesSettingsItem.java | 4 +- .../backend/backup/OsmandSettingsItem.java | 2 +- .../backend/backup/PluginSettingsItem.java | 4 +- .../backup/PoiUiFiltersSettingsItem.java | 4 +- .../backend/backup/ProfileSettingsItem.java | 6 +- .../backup/QuickActionsSettingsItem.java | 4 +- .../backend/backup/ResourcesSettingsItem.java | 4 +- .../backend/backup/SettingsHelper.java | 409 +++++++++--------- .../settings/backend/backup/SettingsItem.java | 6 +- .../backend/backup/SettingsItemType.java | 21 + .../backend/backup/SettingsItemsFactory.java | 4 +- .../backup/SuggestedDownloadsItem.java | 4 +- .../fragments/ImportSettingsFragment.java | 2 +- .../fragments/MainSettingsFragment.java | 2 +- 20 files changed, 257 insertions(+), 246 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java index bda48d389f..004887d8ee 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java @@ -7,5 +7,10 @@ public enum ExportSettingsType { MAP_SOURCES, CUSTOM_RENDER_STYLE, CUSTOM_ROUTING, - AVOID_ROADS + AVOID_ROADS, + MARKERS, + FAVORITES, + TRACKS, + AUDIO_VIDEO_NOTES, + OSM_CHANGES } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java index 3e03fa04fc..3088d9e86f 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/AvoidRoadsSettingsItem.java @@ -46,8 +46,8 @@ public class AvoidRoadsSettingsItem extends CollectionSettingsItem @NonNull @Override - public SettingsHelper.SettingsItemType getType() { - return SettingsHelper.SettingsItemType.MAP_SOURCES; + public SettingsItemType getType() { + return SettingsItemType.MAP_SOURCES; } @Override diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java index b59e89018f..033b856e05 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OsmandSettingsItem.java @@ -22,7 +22,7 @@ public abstract class OsmandSettingsItem extends SettingsItem { this.settings = settings; } - protected OsmandSettingsItem(@NonNull SettingsHelper.SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { + protected OsmandSettingsItem(@NonNull SettingsItemType type, @NonNull OsmandSettings settings, @NonNull JSONObject json) throws JSONException { super(settings.getContext(), json); this.settings = settings; } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java index 335d8952f3..32d6a04e22 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PluginSettingsItem.java @@ -32,8 +32,8 @@ public class PluginSettingsItem extends SettingsItem { @NonNull @Override - public SettingsHelper.SettingsItemType getType() { - return SettingsHelper.SettingsItemType.PLUGIN; + public SettingsItemType getType() { + return SettingsItemType.PLUGIN; } @NonNull diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java index a97c761f39..8d1c464761 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/PoiUiFiltersSettingsItem.java @@ -47,8 +47,8 @@ public class PoiUiFiltersSettingsItem extends CollectionSettingsItem> getSettingsToOperate(List settingsItems, boolean importComplete) { - Map> settingsToOperate = new HashMap<>(); - List profiles = new ArrayList<>(); - List quickActions = new ArrayList<>(); - List poiUIFilters = new ArrayList<>(); - List tileSourceTemplates = new ArrayList<>(); - List routingFilesList = new ArrayList<>(); - List renderFilesList = new ArrayList<>(); - List avoidRoads = new ArrayList<>(); - for (SettingsItem item : settingsItems) { - switch (item.getType()) { - case PROFILE: - profiles.add(((ProfileSettingsItem) item).getModeBean()); - break; - case FILE: - FileSettingsItem fileItem = (FileSettingsItem) item; - if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { - renderFilesList.add(fileItem.getFile()); - } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { - routingFilesList.add(fileItem.getFile()); - } - break; - case QUICK_ACTIONS: - QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; - if (importComplete) { - quickActions.addAll(quickActionsItem.getAppliedItems()); - } else { - quickActions.addAll(quickActionsItem.getItems()); - } - break; - case POI_UI_FILTERS: - PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; - if (importComplete) { - poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); - } else { - poiUIFilters.addAll(poiUiFilterItem.getItems()); - } - break; - case MAP_SOURCES: - MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; - if (importComplete) { - tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); - } else { - tileSourceTemplates.addAll(mapSourcesItem.getItems()); - } - break; - case AVOID_ROADS: - AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; - if (importComplete) { - avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); - } else { - avoidRoads.addAll(avoidRoadsItem.getItems()); - } - break; - default: - break; + @Nullable + public ImportType getImportTaskType() { + ImportAsyncTask importTask = this.importTask; + return importTask != null ? importTask.getImportType() : null; + } + + public boolean isImportDone() { + ImportAsyncTask importTask = this.importTask; + return importTask == null || importTask.isImportDone(); + } + + public boolean isFileExporting(File file) { + return exportAsyncTasks.containsKey(file); + } + + public void updateExportListener(File file, SettingsExportListener listener) { + ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); + if (exportAsyncTask != null) { + exportAsyncTask.listener = listener; + } + } + + private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { + importTask = null; + List warnings = new ArrayList<>(); + for (SettingsItem item : items) { + warnings.addAll(item.getWarnings()); + } + if (!warnings.isEmpty()) { + app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); + } + if (listener != null) { + listener.onSettingsImportFinished(success, items); + } + } + + @SuppressLint("StaticFieldLeak") + private class ImportItemsAsyncTask extends AsyncTask { + + private SettingsImporter importer; + private File file; + private SettingsImportListener listener; + private List items; + + ImportItemsAsyncTask(@NonNull File file, + @Nullable SettingsImportListener listener, + @NonNull List items) { + importer = new SettingsImporter(app); + this.file = file; + this.listener = listener; + this.items = items; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + importer.importItems(file, items); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to import items from: " + file.getName(), e); + } + return false; + } + + @Override + protected void onPostExecute(Boolean success) { + finishImport(listener, success, items); + } + } + + @SuppressLint("StaticFieldLeak") + private class ExportAsyncTask extends AsyncTask { + + private SettingsExporter exporter; + private File file; + private SettingsExportListener listener; + + ExportAsyncTask(@NonNull File settingsFile, + @Nullable SettingsExportListener listener, + @NonNull List items, boolean exportItemsFiles) { + this.file = settingsFile; + this.listener = listener; + this.exporter = new SettingsExporter(exportItemsFiles); + for (SettingsItem item : items) { + exporter.addSettingsItem(item); } } - if (!profiles.isEmpty()) { - settingsToOperate.put(ExportSettingsType.PROFILE, profiles); + @Override + protected Boolean doInBackground(Void... voids) { + try { + exporter.exportSettings(file); + return true; + } catch (JSONException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } catch (IOException e) { + LOG.error("Failed to export items to: " + file.getName(), e); + } + return false; } - if (!quickActions.isEmpty()) { - settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); + + @Override + protected void onPostExecute(Boolean success) { + exportAsyncTasks.remove(file); + if (listener != null) { + listener.onSettingsExportFinished(file, success); + } } - if (!poiUIFilters.isEmpty()) { - settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); - } - if (!tileSourceTemplates.isEmpty()) { - settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); - } - if (!renderFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); - } - if (!routingFilesList.isEmpty()) { - settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); - } - if (!avoidRoads.isEmpty()) { - settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); - } - return settingsToOperate; } @SuppressLint("StaticFieldLeak") @@ -355,123 +380,6 @@ public class SettingsHelper { } } - @Nullable - public ImportAsyncTask getImportTask() { - return importTask; - } - - @Nullable - public ImportType getImportTaskType() { - ImportAsyncTask importTask = this.importTask; - return importTask != null ? importTask.getImportType() : null; - } - - public boolean isImportDone() { - ImportAsyncTask importTask = this.importTask; - return importTask == null || importTask.isImportDone(); - } - - public boolean isFileExporting(File file) { - return exportAsyncTasks.containsKey(file); - } - - public void updateExportListener(File file, SettingsExportListener listener) { - ExportAsyncTask exportAsyncTask = exportAsyncTasks.get(file); - if (exportAsyncTask != null) { - exportAsyncTask.listener = listener; - } - } - - @SuppressLint("StaticFieldLeak") - private class ImportItemsAsyncTask extends AsyncTask { - - private SettingsImporter importer; - private File file; - private SettingsImportListener listener; - private List items; - - ImportItemsAsyncTask(@NonNull File file, - @Nullable SettingsImportListener listener, - @NonNull List items) { - importer = new SettingsImporter(app); - this.file = file; - this.listener = listener; - this.items = items; - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - importer.importItems(file, items); - return true; - } catch (IllegalArgumentException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to import items from: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - finishImport(listener, success, items); - } - } - - private void finishImport(@Nullable SettingsImportListener listener, boolean success, @NonNull List items) { - importTask = null; - List warnings = new ArrayList<>(); - for (SettingsItem item : items) { - warnings.addAll(item.getWarnings()); - } - if (!warnings.isEmpty()) { - app.showToastMessage(AndroidUtils.formatWarnings(warnings).toString()); - } - if (listener != null) { - listener.onSettingsImportFinished(success, items); - } - } - - @SuppressLint("StaticFieldLeak") - private class ExportAsyncTask extends AsyncTask { - - private SettingsExporter exporter; - private File file; - private SettingsExportListener listener; - - ExportAsyncTask(@NonNull File settingsFile, - @Nullable SettingsExportListener listener, - @NonNull List items, boolean exportItemsFiles) { - this.file = settingsFile; - this.listener = listener; - this.exporter = new SettingsExporter(exportItemsFiles); - for (SettingsItem item : items) { - exporter.addSettingsItem(item); - } - } - - @Override - protected Boolean doInBackground(Void... voids) { - try { - exporter.exportSettings(file); - return true; - } catch (JSONException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } catch (IOException e) { - LOG.error("Failed to export items to: " + file.getName(), e); - } - return false; - } - - @Override - protected void onPostExecute(Boolean success) { - exportAsyncTasks.remove(file); - if (listener != null) { - listener.onSettingsExportFinished(file, success); - } - } - } - public void collectSettings(@NonNull File settingsFile, String latestChanges, int version, @Nullable SettingsCollectListener listener) { new ImportAsyncTask(settingsFile, latestChanges, version, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -497,12 +405,6 @@ public class SettingsHelper { exportSettings(fileDir, fileName, listener, new ArrayList<>(Arrays.asList(items)), exportItemsFiles); } - public enum ImportType { - COLLECT, - CHECK_DUPLICATES, - IMPORT - } - public List getFilteredSettingsItems(Map> additionalData, List settingsTypes) { List settingsItems = new ArrayList<>(); @@ -611,4 +513,87 @@ public class SettingsHelper { } return settingsItems; } + + public static Map> getSettingsToOperate(List settingsItems, boolean importComplete) { + Map> settingsToOperate = new HashMap<>(); + List profiles = new ArrayList<>(); + List quickActions = new ArrayList<>(); + List poiUIFilters = new ArrayList<>(); + List tileSourceTemplates = new ArrayList<>(); + List routingFilesList = new ArrayList<>(); + List renderFilesList = new ArrayList<>(); + List avoidRoads = new ArrayList<>(); + for (SettingsItem item : settingsItems) { + switch (item.getType()) { + case PROFILE: + profiles.add(((ProfileSettingsItem) item).getModeBean()); + break; + case FILE: + FileSettingsItem fileItem = (FileSettingsItem) item; + if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.RENDERING_STYLE) { + renderFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { + routingFilesList.add(fileItem.getFile()); + } + break; + case QUICK_ACTIONS: + QuickActionsSettingsItem quickActionsItem = (QuickActionsSettingsItem) item; + if (importComplete) { + quickActions.addAll(quickActionsItem.getAppliedItems()); + } else { + quickActions.addAll(quickActionsItem.getItems()); + } + break; + case POI_UI_FILTERS: + PoiUiFiltersSettingsItem poiUiFilterItem = (PoiUiFiltersSettingsItem) item; + if (importComplete) { + poiUIFilters.addAll(poiUiFilterItem.getAppliedItems()); + } else { + poiUIFilters.addAll(poiUiFilterItem.getItems()); + } + break; + case MAP_SOURCES: + MapSourcesSettingsItem mapSourcesItem = (MapSourcesSettingsItem) item; + if (importComplete) { + tileSourceTemplates.addAll(mapSourcesItem.getAppliedItems()); + } else { + tileSourceTemplates.addAll(mapSourcesItem.getItems()); + } + break; + case AVOID_ROADS: + AvoidRoadsSettingsItem avoidRoadsItem = (AvoidRoadsSettingsItem) item; + if (importComplete) { + avoidRoads.addAll(avoidRoadsItem.getAppliedItems()); + } else { + avoidRoads.addAll(avoidRoadsItem.getItems()); + } + break; + default: + break; + } + } + + if (!profiles.isEmpty()) { + settingsToOperate.put(ExportSettingsType.PROFILE, profiles); + } + if (!quickActions.isEmpty()) { + settingsToOperate.put(ExportSettingsType.QUICK_ACTIONS, quickActions); + } + if (!poiUIFilters.isEmpty()) { + settingsToOperate.put(ExportSettingsType.POI_TYPES, poiUIFilters); + } + if (!tileSourceTemplates.isEmpty()) { + settingsToOperate.put(ExportSettingsType.MAP_SOURCES, tileSourceTemplates); + } + if (!renderFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_RENDER_STYLE, renderFilesList); + } + if (!routingFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.CUSTOM_ROUTING, routingFilesList); + } + if (!avoidRoads.isEmpty()) { + settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); + } + return settingsToOperate; + } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java index 3bc41091e2..caf2d57301 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItem.java @@ -59,7 +59,7 @@ public abstract class SettingsItem { } @NonNull - public abstract SettingsHelper.SettingsItemType getType(); + public abstract SettingsItemType getType(); @NonNull public abstract String getName(); @@ -99,7 +99,7 @@ public abstract class SettingsItem { this.shouldReplace = shouldReplace; } - static SettingsHelper.SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { + static SettingsItemType parseItemType(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { String type = json.has("type") ? json.getString("type") : null; if (type == null) { throw new IllegalArgumentException("No type field"); @@ -107,7 +107,7 @@ public abstract class SettingsItem { if (type.equals("QUICK_ACTION")) { type = "QUICK_ACTIONS"; } - return SettingsHelper.SettingsItemType.valueOf(type); + return SettingsItemType.valueOf(type); } public boolean exists() { diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java new file mode 100644 index 0000000000..163e457e58 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java @@ -0,0 +1,21 @@ +package net.osmand.plus.settings.backend.backup; + +public enum SettingsItemType { + GLOBAL, + PROFILE, + PLUGIN, + DATA, + FILE, + RESOURCES, + QUICK_ACTIONS, + POI_UI_FILTERS, + MAP_SOURCES, + AVOID_ROADS, + SUGGESTED_DOWNLOADS, + DOWNLOADS, + MARKERS, + FAVORITES, + TRACKS, + AUDIO_VIDEO_NOTES, + OSM_CHANGES +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java index 78c90b99ad..d4a639da4d 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemsFactory.java @@ -40,7 +40,7 @@ class SettingsItemsFactory { item = createItem(itemJson); items.add(item); String pluginId = item.getPluginId(); - if (pluginId != null && item.getType() != SettingsHelper.SettingsItemType.PLUGIN) { + if (pluginId != null && item.getType() != SettingsItemType.PLUGIN) { List items = pluginItems.get(pluginId); if (items != null) { items.add(item); @@ -86,7 +86,7 @@ class SettingsItemsFactory { @NonNull private SettingsItem createItem(@NonNull JSONObject json) throws IllegalArgumentException, JSONException { SettingsItem item = null; - SettingsHelper.SettingsItemType type = SettingsItem.parseItemType(json); + SettingsItemType type = SettingsItem.parseItemType(json); OsmandSettings settings = app.getSettings(); switch (type) { case GLOBAL: diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java index 2ff0f88371..d2dd9a8554 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SuggestedDownloadsItem.java @@ -33,8 +33,8 @@ public class SuggestedDownloadsItem extends SettingsItem { @NonNull @Override - public SettingsHelper.SettingsItemType getType() { - return SettingsHelper.SettingsItemType.SUGGESTED_DOWNLOADS; + public SettingsItemType getType() { + return SettingsItemType.SUGGESTED_DOWNLOADS; } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java index 8386f50f73..6002c8a58e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportSettingsFragment.java @@ -52,7 +52,7 @@ import net.osmand.plus.settings.backend.backup.PoiUiFiltersSettingsItem; import net.osmand.plus.settings.backend.backup.ProfileSettingsItem; import net.osmand.plus.settings.backend.backup.QuickActionsSettingsItem; import net.osmand.plus.settings.backend.backup.SettingsItem; -import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsItemType; import net.osmand.plus.widgets.TextViewEx; import net.osmand.util.Algorithms; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java index 3820fd72b8..51a6f43531 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java @@ -19,7 +19,7 @@ import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.settings.backend.backup.SettingsItem; -import net.osmand.plus.settings.backend.backup.SettingsHelper.SettingsItemType; +import net.osmand.plus.settings.backend.backup.SettingsItemType; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; From 93db8c4c0e5739a8567b40c4f1b73aa7e06d427e Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 15 Oct 2020 12:36:33 +0300 Subject: [PATCH 071/123] bug fix --- .../java/net/osmand/osm/io/NetworkUtils.java | 49 ++ OsmAnd/libs/opendb-core.jar | Bin 0 -> 350613 bytes .../plus/mapcontextmenu/MenuBuilder.java | 89 ++- .../plus/osmedit/OpenstreetmapRemoteUtil.java | 78 ++- .../utils/FailedVerificationException.java | 16 + .../plus/osmedit/utils/JsonFormatter.java | 135 +++++ .../osmand/plus/osmedit/utils/SecUtils.java | 411 ++++++++++++++ .../plus/osmedit/utils/ops/OpObject.java | 530 ++++++++++++++++++ .../plus/osmedit/utils/ops/OpOperation.java | 281 ++++++++++ .../osmedit/utils/ops/PerformanceMetrics.java | 171 ++++++ .../plus/osmedit/utils/util/DBConstants.java | 25 + .../osmedit/utils/util/JsonObjectUtils.java | 275 +++++++++ .../plus/osmedit/utils/util/OUtils.java | 122 ++++ .../util/exception/ConnectionException.java | 15 + .../FailedVerificationException.java | 16 + .../util/exception/TechnicalException.java | 15 + 16 files changed, 2200 insertions(+), 28 deletions(-) create mode 100644 OsmAnd/libs/opendb-core.jar create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java 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 28a1ec3fb9..1590116f5c 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 @@ -56,6 +56,55 @@ public class NetworkUtils { return e.getMessage(); } } + + public static String sendPostDataRequest(String urlText, InputStream data){ + try { + log.info("POST : " + urlText); + HttpURLConnection conn = getHttpURLConnection(urlText); + conn.setDoInput(true); + conn.setDoOutput(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept","*/*"); + conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$ + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); + OutputStream ous = conn.getOutputStream(); + ous.write(("--" + BOUNDARY + "\r\n").getBytes()); + ous.write(("content-disposition: form-data; name=\"" + "file" + "\"; filename=\"" + "image1" + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$ + Algorithms.streamCopy(data,ous); + ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.flush(); + log.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage()); + if(conn.getResponseCode() != 200){ + return conn.getResponseMessage(); + } + StringBuilder responseBody = new StringBuilder(); + InputStream is = conn.getInputStream(); + responseBody.setLength(0); + if (is != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); //$NON-NLS-1$ + String s; + boolean first = true; + while ((s = in.readLine()) != null) { + if(first){ + first = false; + } else { + responseBody.append("\n"); //$NON-NLS-1$ + } + responseBody.append(s); + } + is.close(); + } + Algorithms.closeStream(is); + Algorithms.closeStream(data); + Algorithms.closeStream(ous); + return responseBody.toString(); + } catch (IOException e) { + log.error(e.getMessage(), e); + return e.getMessage(); + } + } + private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$ public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, OsmOAuthAuthorizationClient client, diff --git a/OsmAnd/libs/opendb-core.jar b/OsmAnd/libs/opendb-core.jar new file mode 100644 index 0000000000000000000000000000000000000000..ac14553f6d290172c9314326bb8a192590613b92 GIT binary patch literal 350613 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 Um^KauND|yZujLwOSj~ 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

SpD9a-U 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 literal 0 HcmV?d00001 diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 00d0a8cc20..affeba63d1 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -34,17 +34,20 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.Amenity; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadRect; import net.osmand.osm.PoiCategory; +import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard; @@ -53,6 +56,7 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard; import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask; import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard; import net.osmand.plus.mapcontextmenu.controllers.TransportStopController; +import net.osmand.plus.osmedit.utils.SecUtils; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RenderingIcons; import net.osmand.plus.transport.TransportStopRoute; @@ -62,7 +66,9 @@ import net.osmand.plus.widgets.TextViewEx; import net.osmand.plus.widgets.tools.ClickableSpanTouchListener; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; - +import org.apache.commons.logging.Log; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -77,7 +83,7 @@ public class MenuBuilder { public static final float SHADOW_HEIGHT_TOP_DP = 17f; public static final int TITLE_LIMIT = 60; - protected static final String[] arrowChars = new String[]{"=>"," - "}; + protected static final String[] arrowChars = new String[] {"=>", " - "}; protected MapActivity mapActivity; protected MapContextMenu mapContextMenu; @@ -103,6 +109,8 @@ public class MenuBuilder { private String preferredMapLang; private String preferredMapAppLang; private boolean transliterateNames; + private static final int PICK_IMAGE = 1231; + private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); public interface CollapseExpandListener { void onCollapseExpand(boolean collapsed); @@ -212,10 +220,59 @@ public class MenuBuilder { if (showOnlinePhotos) { buildNearestPhotosRow(view); } + buildUploadImagesRow(view); buildPluginRows(view); // buildAfter(view); } + public void buildUploadImagesRow(View view) { + if (mapContextMenu != null) { + //TODO to strings + String title = "Upload images"; + buildRow(view, R.drawable.ic_action_note_dark, null, title, 0, false, + null, false, 0, false, new OnClickListener() { + @Override + public void onClick(View view) { + mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, + new ActivityResultListener.OnActivityResultListener() { + @Override + public void onResult(int resultCode, Intent resultData) { + InputStream inputStream = null; + try { + inputStream = mapActivity.getContentResolver().openInputStream(resultData.getData()); + } catch (FileNotFoundException e) { + LOG.error(e); + } + handleSelectedImage(inputStream); + } + })); + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + mapActivity.startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE); + } + }, false); + } + } + + private void handleSelectedImage(final InputStream image) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try{ + String url = "https://test.openplacereviews.org/api/ipfs/image"; + String response = NetworkUtils.sendPostDataRequest(url, image); + //TODO + (new SecUtils()).main(new String[0]); + } + catch (Exception e){ + e.printStackTrace(); + } + } + }); + t.start(); + } + private boolean showTransportRoutes() { return showLocalTransportRoutes() || showNearbyTransportRoutes(); } @@ -254,7 +311,7 @@ public class MenuBuilder { protected boolean needBuildPlainMenuItems() { return true; } - + protected boolean needBuildCoordinatesRow() { return true; } @@ -282,7 +339,7 @@ public class MenuBuilder { protected void buildNearestWikiRow(View view) { if (processNearestWiki() && nearestWiki.size() > 0) { - buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size()+")", 0, + buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", 0, true, getCollapsableWikiView(view.getContext(), true), false, 0, false, null, false); } @@ -322,9 +379,9 @@ public class MenuBuilder { locationData.remove(PointDescription.LOCATION_LIST_HEADER); CollapsableView cv = getLocationCollapsableView(locationData); buildRow(view, R.drawable.ic_action_get_my_location, null, title, 0, true, cv, false, 1, - false, null, false); + false, null, false); } - + private void startLoadingImages() { if (onlinePhotoCardsRow == null) { return; @@ -379,7 +436,7 @@ public class MenuBuilder { } } - protected void buildDescription(View view){ + protected void buildDescription(View view) { } protected void buildAfter(View view) { @@ -395,8 +452,8 @@ public class MenuBuilder { } public View buildRow(View view, int iconId, String buttonText, String text, int textColor, - boolean collapsable, final CollapsableView collapsableView, - boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { + boolean collapsable, final CollapsableView collapsableView, + boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { return buildRow(view, iconId == 0 ? null : getRowIcon(iconId), buttonText, text, textColor, null, collapsable, collapsableView, needLinks, textLinesLimit, isUrl, onClickListener, matchWidthDivider); } @@ -480,7 +537,7 @@ public class MenuBuilder { textPrefixView.setLayoutParams(llTextParams); textPrefixView.setTypeface(FontCache.getRobotoRegular(view.getContext())); textPrefixView.setTextSize(12); - textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); + textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); textPrefixView.setMinLines(1); textPrefixView.setMaxLines(1); textPrefixView.setText(textPrefix); @@ -526,7 +583,7 @@ public class MenuBuilder { textViewSecondary.setLayoutParams(llTextSecondaryParams); textViewSecondary.setTypeface(FontCache.getRobotoRegular(view.getContext())); textViewSecondary.setTextSize(14); - textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); + textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); textViewSecondary.setText(secondaryText); llText.addView(textViewSecondary); } @@ -581,7 +638,7 @@ public class MenuBuilder { } if (collapsableView.getContentView().getParent() != null) { ((ViewGroup) collapsableView.getContentView().getParent()) - .removeView(collapsableView.getContentView()); + .removeView(collapsableView.getContentView()); } baseView.addView(collapsableView.getContentView()); } @@ -741,8 +798,8 @@ public class MenuBuilder { } public void addPlainMenuItem(int iconId, String text, boolean needLinks, boolean isUrl, - boolean collapsable, CollapsableView collapsableView, - OnClickListener onClickListener) { + boolean collapsable, CollapsableView collapsableView, + OnClickListener onClickListener) { plainMenuItems.add(new PlainMenuItem(iconId, null, text, needLinks, isUrl, collapsable, collapsableView, onClickListener)); } @@ -964,7 +1021,7 @@ public class MenuBuilder { button.setTypeface(FontCache.getRobotoRegular(context)); int bg; if (selected) { - bg = light ? R.drawable.context_menu_controller_bg_light_selected: R.drawable.context_menu_controller_bg_dark_selected; + bg = light ? R.drawable.context_menu_controller_bg_light_selected : R.drawable.context_menu_controller_bg_dark_selected; } else if (showAll) { bg = light ? R.drawable.context_menu_controller_bg_light_show_all : R.drawable.context_menu_controller_bg_dark_show_all; } else { @@ -1021,7 +1078,7 @@ public class MenuBuilder { private List getAmenities(QuadRect rect, PoiUIFilter wikiPoiFilter) { return wikiPoiFilter.searchAmenities(rect.top, rect.left, - rect.bottom, rect.right, -1, null); + rect.bottom, rect.right, -1, null); } @SuppressWarnings("unchecked") diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java index 393dd7d965..6e38a115ea 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java @@ -18,6 +18,7 @@ import net.osmand.osm.edit.Entity.EntityType; import net.osmand.osm.edit.EntityInfo; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; +import net.osmand.osm.io.Base64; import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.plus.OsmandApplication; @@ -30,10 +31,8 @@ import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; +import java.io.*; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.HashMap; @@ -108,14 +107,16 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { boolean doAuthenticate) { log.info("Sending request " + url); //$NON-NLS-1$ try { - if (doAuthenticate){ - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequest(url,requestMethod,requestBody); - return response.getBody(); - } - else { - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody); + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + if (doAuthenticate) { + if (client.isValidToken()) { + Response response = client.performRequest(url, requestMethod, requestBody); + return response.getBody(); + } else { + return performBasicAuthRequest(url, requestMethod, requestBody, userOperation); + } + } else { + Response response = client.performRequestWithoutAuth(url, requestMethod, requestBody); return response.getBody(); } } catch (NullPointerException e) { @@ -139,11 +140,64 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); + } catch (Exception e) { + log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ + showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); } return null; } + private String performBasicAuthRequest(String url, String requestMethod, String requestBody, String userOperation) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); + connection.setConnectTimeout(15000); + connection.setRequestMethod(requestMethod); + connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$ + StringBuilder responseBody = new StringBuilder(); + String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$ + connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoInput(true); + if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoOutput(true); + connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$ + OutputStream out = connection.getOutputStream(); + if (requestBody != null) { + BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$ + bwr.write(requestBody); + bwr.flush(); + } + out.close(); + } + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + String msg = userOperation + + " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$ + log.error(msg); + showWarning(msg); + } else { + log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$ + // populate return fields. + responseBody.setLength(0); + InputStream i = connection.getInputStream(); + if (i != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$ + String s; + boolean f = true; + while ((s = in.readLine()) != null) { + if (!f) { + responseBody.append("\n"); //$NON-NLS-1$ + } else { + f = false; + } + responseBody.append(s); + } + } + return responseBody.toString(); + } + return null; + } + public long openChangeSet(String comment) { long id = -1; StringWriter writer = new StringWriter(256); diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java new file mode 100644 index 0000000000..08f1a71cec --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java @@ -0,0 +1,16 @@ +package net.osmand.plus.osmedit.utils; + +public class FailedVerificationException extends Exception { + + private static final long serialVersionUID = -4936205097177668159L; + + + public FailedVerificationException(Exception e) { + super(e); + } + + + public FailedVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java new file mode 100644 index 0000000000..74aca61659 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java @@ -0,0 +1,135 @@ +package net.osmand.plus.osmedit.utils; + +import com.google.gson.*; +import net.osmand.plus.osmedit.utils.ops.OpObject; +import net.osmand.plus.osmedit.utils.ops.OpOperation; + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.*; + +public class JsonFormatter { + + private Gson gson; + + private Gson gsonOperationHash; + + private Gson gsonFullOutput; + + public JsonFormatter() { + GsonBuilder builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gson = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false, true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonOperationHash = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(true)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonFullOutput = builder.create(); + + + } + + public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer> { + + @Override @SuppressWarnings("unchecked") + public TreeMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return (TreeMap) read(json); + } + + public Object read(JsonElement in) { + + if(in.isJsonArray()){ + List list = new ArrayList(); + JsonArray arr = in.getAsJsonArray(); + for (JsonElement anArr : arr) { + list.add(read(anArr)); + } + return list; + }else if(in.isJsonObject()){ + Map map = new TreeMap(); + JsonObject obj = in.getAsJsonObject(); + Set> entitySet = obj.entrySet(); + for(Map.Entry entry: entitySet){ + map.put(entry.getKey(), read(entry.getValue())); + } + return map; + }else if(in.isJsonPrimitive()){ + JsonPrimitive prim = in.getAsJsonPrimitive(); + if(prim.isBoolean()){ + return prim.getAsBoolean(); + }else if(prim.isString()){ + return prim.getAsString(); + }else if(prim.isNumber()){ + Number num = prim.getAsNumber(); + // here you can handle double int/long values + // and return any type you want + // this solution will transform 3.0 float to long values + if(Math.ceil(num.doubleValue()) == num.longValue() && (!num.toString().contains(".") || num.toString().split("\\.")[1].length() <= 1)) + return num.longValue(); + else { + return num.doubleValue(); + } + } + } + return null; + } + } + +// operations to parse / format related + public OpOperation parseOperation(String opJson) { + return gson.fromJson(opJson, OpOperation.class); + } + + public OpObject parseObject(String opJson) { + return gson.fromJson(opJson, OpObject.class); + } + + public Object parseBlock(String opJson) { + throw new UnsupportedOperationException(""); + } + + public JsonElement toJsonElement(Object o) { + return gson.toJsonTree(o); + } + + @SuppressWarnings("unchecked") + public TreeMap fromJsonToTreeMap(String json) { + return gson.fromJson(json, TreeMap.class); + } + + + public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException { + return gson.fromJson(json, classOfT); + } + + public String fullObjectToJson(Object o) { + return gsonFullOutput.toJson(o); + } + + + public String opToJsonNoHash(OpOperation op) { + return gsonOperationHash.toJson(op); + } + + public String opToJson(OpOperation op) { + return gson.toJson(op); + } + + public String objToJson(OpObject op) { + return gson.toJson(op); + } + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java new file mode 100644 index 0000000000..d0f0765bb6 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java @@ -0,0 +1,411 @@ +package net.osmand.plus.osmedit.utils; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +//import java.util.Base64; +import java.util.concurrent.ThreadLocalRandom; + +import net.osmand.plus.osmedit.utils.ops.OpOperation; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +//import org.bouncycastle.crypto.generators.SCrypt; +//import org.bouncycastle.crypto.prng.FixedSecureRandom; +public class SecUtils { + private static final String SIG_ALGO_SHA1_EC = "SHA1withECDSA"; + private static final String SIG_ALGO_NONE_EC = "NonewithECDSA"; + + public static final String SIG_ALGO_ECDSA = "ECDSA"; + public static final String ALGO_EC = "EC"; + public static final String EC_256SPEC_K1 = "secp256k1"; + + public static final String KEYGEN_PWD_METHOD_1 = "EC256K1_S17R8"; + public static final String DECODE_BASE64 = "base64"; + public static final String HASH_SHA256 = "sha256"; + public static final String HASH_SHA1 = "sha1"; + + public static final String JSON_MSG_TYPE = "json"; + public static final String KEY_BASE64 = DECODE_BASE64; + + public static void main(String[] args) { + //1) create op, 2) sign op 3) send to server process op + // + KeyPairGenerator keyGen = null ; + SecureRandom random = null; + try { + keyGen = KeyPairGenerator.getInstance(ALGO_EC); + random = SecureRandom.getInstance("SHA1PRNG"); + keyGen.initialize(1024, random); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + KeyPair kp = null; + try { + kp = SecUtils.getKeyPair(ALGO_EC, + "base64:PKCS#8:MD4CAQAwEAYHKoZIzj0CAQYFK4EEAAoEJzAlAgEBBCDR+/ByIjTHZgfdnMfP9Ab5s14mMzFX+8DYqUiGmf/3rw==" + , "base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEOMUiRZwU7wW8L3A1qaJPwhAZy250VaSxJmKCiWdn9EMeubXQgWNT8XUWLV5Nvg7O3sD+1AAQLG5kHY8nOc/AyA=="); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } +// KeyPair kp = generateECKeyPairFromPassword(KEYGEN_PWD_METHOD_1, "openplacereviews", ""); +// KeyPair kp = generateRandomEC256K1KeyPair(); + System.out.println(kp.getPrivate().getFormat()); + System.out.println(kp.getPrivate().getAlgorithm()); + try { + System.out.println(SecUtils.validateKeyPair(ALGO_EC, kp.getPrivate(), kp.getPublic())); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } + String pr = encodeKey(KEY_BASE64, kp.getPrivate()); + String pk = encodeKey(KEY_BASE64, kp.getPublic()); + String algo = kp.getPrivate().getAlgorithm(); + System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", kp.getPrivate().getFormat(), pr, kp + .getPublic().getFormat(), pk)); + String signMessageTest = "Hello this is a registration message test"; + byte[] signature = signMessageWithKey(kp, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC); + System.out.println(String.format("Signed message: %s %s", android.util.Base64.decode(signature, android.util.Base64.DEFAULT), + signMessageTest)); + + KeyPair nk = null; + try { + nk = getKeyPair(algo, pr, pk); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } + // validate + pr = new String(android.util.Base64.decode(nk.getPrivate().getEncoded(), android.util.Base64.DEFAULT)); + pk = new String(android.util.Base64.decode(nk.getPublic().getEncoded(), android.util.Base64.DEFAULT)); + + System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", nk.getPrivate().getFormat(), pr, nk + .getPublic().getFormat(), pk)); + System.out.println(validateSignature(nk, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC, signature)); + + JsonFormatter formatter = new JsonFormatter(); + String msg = "{\n" + + " \"type\" : \"sys.signup\",\n" + + " \"signed_by\": \"openplacereviews\",\n" + + " \"create\": [{\n" + + " \"id\": [\"openplacereviews\"],\n" + + " \"name\" : \"openplacereviews\",\n" + + " \"algo\": \"EC\",\n" + + " \"auth_method\": \"provided\",\n" + + " \"pubkey\": \"base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEn6GkOTN3SYc+OyCYCpqPzKPALvUgfUVNDJ+6eyBlCHI1/gKcVqzHLwaO90ksb29RYBiF4fW/PqHcECNzwJB+QA==\"\n" + + " }]\n" + + " }"; + + OpOperation opOperation = formatter.parseOperation(msg); + String hash = JSON_MSG_TYPE + ":" + + SecUtils.calculateHashWithAlgo(SecUtils.HASH_SHA256, null, + formatter.opToJsonNoHash(opOperation)); + + byte[] hashBytes = SecUtils.getHashBytes(hash); + String signatureTxt = SecUtils.signMessageWithKeyBase64(kp, hashBytes, SecUtils.SIG_ALGO_ECDSA, null); + System.out.println(formatter.opToJsonNoHash(opOperation)); + System.out.println(hash); + System.out.println(signatureTxt); + } + + public static EncodedKeySpec decodeKey(String key) { + if (key.startsWith(KEY_BASE64 + ":")) { + key = key.substring(KEY_BASE64.length() + 1); + int s = key.indexOf(':'); + if (s == -1) { + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + return getKeySpecByFormat(key.substring(0, s), + android.util.Base64.decode(key.substring(s + 1), android.util.Base64.DEFAULT)); + } + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + + public static String encodeKey(String algo, PublicKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static String encodeKey(String algo, PrivateKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static EncodedKeySpec getKeySpecByFormat(String format, byte[] data) { + switch (format) { + case "PKCS#8": + return new PKCS8EncodedKeySpec(data); + case "X.509": + return new X509EncodedKeySpec(data); + } + throw new IllegalArgumentException(format); + } + + public static String encodeBase64(byte[] data) { + return new String(android.util.Base64.decode(data, android.util.Base64.DEFAULT)); + } + + public static boolean validateKeyPair(String algo, PrivateKey privateKey, PublicKey publicKey) + throws FailedVerificationException { + if (!algo.equals(ALGO_EC)) { + throw new FailedVerificationException("Algorithm is not supported: " + algo); + } + // create a challenge + byte[] challenge = new byte[512]; + ThreadLocalRandom.current().nextBytes(challenge); + + try { + // sign using the private key + Signature sig = Signature.getInstance(SIG_ALGO_SHA1_EC); + sig.initSign(privateKey); + sig.update(challenge); + byte[] signature = sig.sign(); + + // verify signature using the public key + sig.initVerify(publicKey); + sig.update(challenge); + + boolean keyPairMatches = sig.verify(signature); + return keyPairMatches; + } catch (InvalidKeyException e) { + throw new FailedVerificationException(e); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (SignatureException e) { + throw new FailedVerificationException(e); + } + } + + public static KeyPair getKeyPair(String algo, String prKey, String pbKey) throws FailedVerificationException { + try { + KeyFactory keyFactory = KeyFactory.getInstance(algo); + PublicKey pb = null; + PrivateKey pr = null; + if (pbKey != null) { + pb = keyFactory.generatePublic(decodeKey(pbKey)); + } + if (prKey != null) { + pr = keyFactory.generatePrivate(decodeKey(prKey)); + } + return new KeyPair(pb, pr); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidKeySpecException e) { + throw new FailedVerificationException(e); + } + } + + public static KeyPair generateKeyPairFromPassword(String algo, String keygenMethod, String salt, String pwd) + throws FailedVerificationException { + if (algo.equals(ALGO_EC)) { + return generateECKeyPairFromPassword(keygenMethod, salt, pwd); + } + throw new UnsupportedOperationException("Unsupported algo keygen method: " + algo); + } + + public static KeyPair generateECKeyPairFromPassword(String keygenMethod, String salt, String pwd) + throws FailedVerificationException { + if (keygenMethod.equals(KEYGEN_PWD_METHOD_1)) { + return generateEC256K1KeyPairFromPassword(salt, pwd); + } + throw new UnsupportedOperationException("Unsupported keygen method: " + keygenMethod); + } + + // "EC:secp256k1:scrypt(salt,N:17,r:8,p:1,len:256)" algorithm - EC256K1_S17R8 + public static KeyPair generateEC256K1KeyPairFromPassword(String salt, String pwd) + throws FailedVerificationException { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); + ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); + if (pwd.length() < 10) { + throw new IllegalArgumentException("Less than 10 characters produces only 50 bit entropy"); + } + byte[] bytes = pwd.getBytes("UTF-8"); + //byte[] scrypt = SCrypt.generate(bytes, salt.getBytes("UTF-8"), 1 << 17, 8, 1, 256); + //kpg.initialize(ecSpec, new FixedSecureRandom(scrypt)); + return kpg.genKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (UnsupportedEncodingException e) { + throw new FailedVerificationException(e); + } /* catch (InvalidAlgorithmParameterException e) { + throw new FailedVerificationException(e); + }*/ + } + + public static KeyPair generateRandomEC256K1KeyPair() throws FailedVerificationException { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); + ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); + kpg.initialize(ecSpec); + return kpg.genKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new FailedVerificationException(e); + } + } + + public static String signMessageWithKeyBase64(KeyPair keyPair, byte[] msg, String signAlgo, ByteArrayOutputStream out) { + byte[] sigBytes = signMessageWithKey(keyPair, msg, signAlgo); + if(out != null) { + try { + out.write(sigBytes); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + String signature = new String(android.util.Base64.decode(sigBytes, android.util.Base64.DEFAULT)); + return signAlgo + ":" + DECODE_BASE64 + ":" + signature; + } + + public static byte[] signMessageWithKey(KeyPair keyPair, byte[] msg, String signAlgo) { + try { + Signature sig = Signature.getInstance(getInternalSigAlgo(signAlgo)); + sig.initSign(keyPair.getPrivate()); + sig.update(msg); + byte[] signatureBytes = sig.sign(); + return signatureBytes; + } catch (NoSuchAlgorithmException e) { + //throw new FailedVerificationException(e); + } catch (InvalidKeyException e) { + //throw new FailedVerificationException(e); + } catch (SignatureException e) { + //throw new FailedVerificationException(e); + } + return new byte[0]; + } + + public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sig) { + if(sig == null || keyPair == null) { + return false; + } + int ind = sig.indexOf(':'); + String sigAlgo = sig.substring(0, ind); + return validateSignature(keyPair, msg, sigAlgo, decodeSignature(sig.substring(ind + 1))); + } + + public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sigAlgo, byte[] signature) { + if (keyPair == null) { + return false; + } + try { + Signature sig = Signature.getInstance(getInternalSigAlgo(sigAlgo)); + sig.initVerify(keyPair.getPublic()); + sig.update(msg); + return sig.verify(signature); + } catch (NoSuchAlgorithmException e) { + //throw new FailedVerificationException(e); + } catch (InvalidKeyException e) { + //throw new FailedVerificationException(e); + } catch (SignatureException e) { + //throw new FailedVerificationException(e); + } + return false; + } + + private static String getInternalSigAlgo(String sigAlgo) { + return sigAlgo.equals(SIG_ALGO_ECDSA)? SIG_ALGO_NONE_EC : sigAlgo; + } + + + public static byte[] calculateHash(String algo, byte[] b1, byte[] b2) { + byte[] m = mergeTwoArrays(b1, b2); + if (algo.equals(HASH_SHA256)) { + return DigestUtils.sha256(m); + } else if (algo.equals(HASH_SHA1)) { + return DigestUtils.sha1(m); + } + throw new UnsupportedOperationException(); + } + + public static byte[] mergeTwoArrays(byte[] b1, byte[] b2) { + byte[] m = b1 == null ? b2 : b1; + if(b2 != null && b1 != null) { + m = new byte[b1.length + b2.length]; + System.arraycopy(b1, 0, m, 0, b1.length); + System.arraycopy(b2, 0, m, b1.length, b2.length); + } + return m; + } + + public static String calculateHashWithAlgo(String algo, String salt, String msg) { + try { + String hex = Hex.encodeHexString(calculateHash(algo, salt == null ? null : salt.getBytes("UTF-8"), + msg == null ? null : msg.getBytes("UTF-8"))); + return algo + ":" + hex; + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public static String calculateHashWithAlgo(String algo, byte[] bts) { + byte[] hash = calculateHash(algo, bts, null); + return formatHashWithAlgo(algo, hash); + } + + public static String formatHashWithAlgo(String algo, byte[] hash) { + String hex = Hex.encodeHexString(hash); + return algo + ":" + hex; + } + + public static byte[] getHashBytes(String msg) { + if(msg == null || msg.length() == 0) { + // special case for empty hash + return new byte[0]; + } + int i = msg.lastIndexOf(':'); + String s = i >= 0 ? msg.substring(i + 1) : msg; + try { + return Hex.decodeHex(s); + } catch (DecoderException e) { + throw new IllegalArgumentException(e); + } + } + + + public static boolean validateHash(String hash, String salt, String msg) { + int s = hash.indexOf(":"); + if (s == -1) { + throw new IllegalArgumentException(String.format("Hash %s doesn't contain algorithm of hashing to verify", + s)); + } + String v = calculateHashWithAlgo(hash.substring(0, s), salt, msg); + return hash.equals(v); + } + + public static byte[] decodeSignature(String digest) { + try { + int indexOf = digest.indexOf(DECODE_BASE64 + ":"); + if (indexOf != -1) { +// return Base64.getDecoder().decode(digest.substring(indexOf + DECODE_BASE64.length() + 1). +// getBytes("UTF-8")); + return android.util.Base64.decode(digest.substring(indexOf + DECODE_BASE64.length() + 1) + .getBytes("UTF-8"), android.util.Base64.DEFAULT); + } + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + throw new IllegalArgumentException("Unknown format for signature " + digest); + } + + public static String hexify(byte[] bytes) { + if(bytes == null || bytes.length == 0) { + return ""; + } + return Hex.encodeHexString(bytes); + + } + + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java new file mode 100644 index 0000000000..20350f6572 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java @@ -0,0 +1,530 @@ +package net.osmand.plus.osmedit.utils.ops; + +import com.google.gson.*; +import net.osmand.plus.osmedit.utils.util.JsonObjectUtils; +import net.osmand.plus.osmedit.utils.util.OUtils; + +import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public class OpObject { + + public static final String F_NAME = "name"; + public static final String F_ID = "id"; + public static final String F_COMMENT = "comment"; + public static final String TYPE_OP = "sys.op"; + public static final String TYPE_BLOCK = "sys.block"; + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + // transient info about validation timing etc + public static final String F_EVAL = "eval"; + public static final String F_VALIDATION = "validation"; + public static final String F_TIMESTAMP_ADDED = "timestamp"; + public static final String F_PARENT_TYPE = "parentType"; + public static final String F_PARENT_HASH = "parentHash"; + public static final String F_CHANGE = "change"; + public static final String F_CURRENT = "current"; + // voting + public static final String F_OP = "op"; + public static final String F_STATE = "state"; + public static final String F_OPEN = "open"; + public static final String F_FINAL = "final"; + public static final String F_VOTE = "vote"; + public static final String F_VOTES = "votes"; + public static final String F_SUBMITTED_OP_HASH = "submittedOpHash"; + public static final String F_USER = "user"; + + public static final OpObject NULL = new OpObject(true); + + public static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + static { + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + protected Map fields = new TreeMap<>(); + protected transient Map cacheFields; + protected boolean isImmutable; + + protected transient String parentType; + protected transient String parentHash; + protected transient boolean deleted; + + + public OpObject() {} + + public OpObject(boolean deleted) { + this.deleted = deleted; + } + + public OpObject(OpObject cp) { + this(cp, false); + } + + public OpObject(OpObject cp, boolean copyCacheFields) { + createOpObjectCopy(cp, copyCacheFields); + } + + @SuppressWarnings("unchecked") + private OpObject createOpObjectCopy(OpObject opObject, boolean copyCacheFields) { + this.parentType = opObject.parentType; + this.parentHash = opObject.parentHash; + this.deleted = opObject.deleted; + this.fields = (Map) copyingObjects(opObject.fields, copyCacheFields); + if (opObject.cacheFields != null && copyCacheFields) { + this.cacheFields = (Map) copyingObjects(opObject.cacheFields, copyCacheFields); + } + this.isImmutable = false; + + return this; + } + + public boolean isDeleted() { + return deleted; + } + + @SuppressWarnings("unchecked") + private Object copyingObjects(Object object, boolean copyCacheFields) { + if (object instanceof Number) { + return (Number) object; + } else if (object instanceof String) { + return (String) object; + } else if (object instanceof Boolean) { + return (Boolean) object; + } else if (object instanceof List) { + List copy = new ArrayList<>(); + List list = (List) object; + for (Object o : list) { + copy.add(copyingObjects(o, copyCacheFields)); + } + return copy; + } else if (object instanceof Map) { + Map copy = new LinkedHashMap<>(); + Map map = (Map) object; + for (Object o : map.keySet()) { + copy.put(o, copyingObjects(map.get(o), copyCacheFields)); + } + return copy; + } else if (object instanceof OpObject) { + return new OpObject((OpObject) object); + } else { + throw new UnsupportedOperationException("Type of object is not supported"); + } + } + + public void setParentOp(OpOperation op) { + setParentOp(op.type, op.getRawHash()); + } + + public void setParentOp(String parentType, String parentHash) { + this.parentType = parentType; + this.parentHash = parentHash; + } + + public String getParentHash() { + return parentHash; + } + + public String getParentType() { + return parentType; + } + + public List getId() { + return getStringList(F_ID); + } + + public void setId(String id) { + addOrSetStringValue(F_ID, id);; + } + + public boolean isImmutable() { + return isImmutable; + } + + public OpObject makeImmutable() { + isImmutable = true; + return this; + } + + public Object getFieldByExpr(String field) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + return JsonObjectUtils.getField(this.fields, generateFieldSequence(field)); + } + + return fields.get(field); + } + + + /** + * generateFieldSequence("a") - [a] + * generateFieldSequence("a.b") - [a, b] + * generateFieldSequence("a.b.c.de") - [a, b, c, de] + * generateFieldSequence("a.bwerq.c") - [a, bwerq, c] + * generateFieldSequence("a.bwerq...c") - [a, bwerq, c] + * generateFieldSequence("a.bwereq..c..") - [a, bwerq, c] + * generateFieldSequence("a.{b}") - [a, b] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b{}}") - [a, b{}] + * generateFieldSequence("a.{b{}d.q}") - [a, b{}d.q] + */ + private static List generateFieldSequence(String field) { + int STATE_OPEN_BRACE = 1; + int STATE_OPEN = 0; + int state = STATE_OPEN; + int start = 0; + List l = new ArrayList(); + for(int i = 0; i < field.length(); i++) { + boolean split = false; + if (i == field.length() - 1) { + if (state == STATE_OPEN_BRACE) { + if(field.charAt(i) == '}') { + split = true; + } else { + throw new IllegalArgumentException("Illegal field expression: " + field); + } + } else { + if(field.charAt(i) != '.') { + i++; + } + split = true; + } + } else { + if (field.charAt(i) == '.' && state == STATE_OPEN) { + split = true; + } else if (field.charAt(i) == '}' && field.charAt(i + 1) == '.' && state == STATE_OPEN_BRACE) { + split = true; + } else if (field.charAt(i) == '{' && state == STATE_OPEN) { + if(start != i) { + throw new IllegalArgumentException("Illegal field expression (wrap {} is necessary): " + field); + } + state = STATE_OPEN_BRACE; + start = i + 1; + } + } + if(split) { + if (i != start) { + l.add(field.substring(start, i)); + } + start = i + 1; + state = STATE_OPEN; + } + } + return l; + } + + public void setFieldByExpr(String field, Object object) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + List fieldSequence = generateFieldSequence(field); + if (object == null) { + JsonObjectUtils.deleteField(this.fields, fieldSequence); + } else { + JsonObjectUtils.setField(this.fields, fieldSequence, object); + } + } else if (object == null) { + fields.remove(field); + } else { + fields.put(field, object); + } + } + + + public Object getCacheObject(String f) { + if(cacheFields == null) { + return null; + } + return cacheFields.get(f); + } + + public void putCacheObject(String f, Object o) { + if (isImmutable()) { + if (cacheFields == null) { + cacheFields = new ConcurrentHashMap(); + } + cacheFields.put(f, o); + } + } + + public void setId(String id, String id2) { + List list = new ArrayList(); + list.add(id); + list.add(id2); + putObjectValue(F_ID, list); + } + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + public Map getRawOtherFields() { + return fields; + } + + @SuppressWarnings("unchecked") + public Map getStringMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map> getMapStringList(String field) { + return (Map>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringObjMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map getStringObjMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public T getField(T def, String... fields) { + Map p = this.fields; + for(int i = 0; i < fields.length - 1 ; i++) { + p = (Map) p.get(fields[i]); + if(p == null) { + return def; + } + } + T res = (T) p.get(fields[fields.length - 1]); + if(res == null) { + return def; + } + return res; + } + + @SuppressWarnings("unchecked") + public Map, Object> getStringListObjMap(String field) { + return (Map, Object>) fields.get(field); + } + + + public long getDate(String field) { + String date = getStringValue(field); + if(OUtils.isEmpty(date)) { + return 0; + } + try { + return dateFormat.parse(date).getTime(); + } catch (ParseException e) { + return 0; + } + } + + + public void setDate(String field, long time) { + putStringValue(field, dateFormat.format(new Date(time))); + } + + public Number getNumberValue(String field) { + return (Number) fields.get(field); + } + + public int getIntValue(String key, int def) { + Number o = getNumberValue(key); + return o == null ? def : o.intValue(); + } + + public long getLongValue(String key, long def) { + Number o = getNumberValue(key); + return o == null ? def : o.longValue(); + } + + public String getStringValue(String field) { + Object o = fields.get(field); + if (o instanceof String || o == null) { + return (String) o; + } + return o.toString(); + } + + @SuppressWarnings("unchecked") + public List getStringList(String field) { + // cast to list if it is single value + Object o = fields.get(field); + if(o == null || o.toString().isEmpty()) { + return Collections.emptyList(); + } + if(o instanceof String) { + return Collections.singletonList(o.toString()); + } + return (List) o; + } + + public Object getObjectValue(String field) { + return fields.get(field); + } + + public void putStringValue(String key, String value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + /** + * Operates as a single value if cardinality is less than 1 + * or as a list of values if it stores > 1 value + * @param key + * @param value + */ + @SuppressWarnings("unchecked") + public void addOrSetStringValue(String key, String value) { + checkNotImmutable(); + Object o = fields.get(key); + if(o == null) { + fields.put(key, value); + } else if(o instanceof List) { + ((List) o).add(value); + } else { + List list = new ArrayList(); + list.add(o.toString()); + list.add(value); + fields.put(key, list); + } + } + + @SuppressWarnings("unchecked") + public Map getChangedEditFields() { + return (Map) fields.get(F_CHANGE); + } + + @SuppressWarnings("unchecked") + public Map getCurrentEditFields() { + return (Map) fields.get(F_CURRENT); + } + + public void putObjectValue(String key, Object value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + public void checkNotImmutable() { + if(isImmutable) { + throw new IllegalStateException("Object is immutable"); + } + + } + + public void checkImmutable() { + if(!isImmutable) { + throw new IllegalStateException("Object is mutable"); + } + } + + public Object remove(String key) { + checkNotImmutable(); + return fields.remove(key); + } + + public Map getMixedFieldsAndCacheMap() { + TreeMap mp = new TreeMap<>(fields); + if(cacheFields != null || parentType != null || parentHash != null) { + TreeMap eval = new TreeMap(); + + if(parentType != null) { + eval.put(F_PARENT_TYPE, parentType); + } + if(parentHash != null) { + eval.put(F_PARENT_HASH, parentHash); + } + if (cacheFields != null) { + Iterator> it = cacheFields.entrySet().iterator(); + while (it.hasNext()) { + Entry e = it.next(); + Object v = e.getValue(); + if (v instanceof Map || v instanceof String || v instanceof Number) { + eval.put(e.getKey(), v); + } + } + } + if(eval.size() > 0) { + mp.put(F_EVAL, eval); + } + } + return mp; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((fields == null) ? 0 : fields.hashCode()); + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + fields + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OpObject other = (OpObject) obj; + if (fields == null) { + if (other.fields != null) + return false; + } else if (!fields.equals(other.fields)) + return false; + return true; + } + + public static class OpObjectAdapter implements JsonDeserializer, + JsonSerializer { + + private boolean fullOutput; + + public OpObjectAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + } + + @Override + public OpObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + OpObject bn = new OpObject(); + bn.fields = context.deserialize(json, TreeMap.class); + // remove cache + bn.fields.remove(F_EVAL); + return bn; + } + + @Override + public JsonElement serialize(OpObject src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + } + + + } + + + + + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java new file mode 100644 index 0000000000..ad1c9571ae --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java @@ -0,0 +1,281 @@ +package net.osmand.plus.osmedit.utils.ops; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.util.*; + +public class OpOperation extends OpObject { + + public static final String F_TYPE = "type"; + public static final String F_SIGNED_BY = "signed_by"; + public static final String F_HASH = "hash"; + + public static final String F_SIGNATURE = "signature"; + + public static final String F_REF = "ref"; + public static final String F_CREATE = "create"; + public static final String F_DELETE = "delete"; + public static final String F_EDIT = "edit"; + + public static final String F_NAME = "name"; + public static final String F_COMMENT = "comment"; + + private List createdObjects = new LinkedList(); + private List editedObjects = new LinkedList(); + protected String type; + + public OpOperation() { + } + + public OpOperation(OpOperation cp, boolean copyCacheFields) { + super(cp, copyCacheFields); + this.type = cp.type; + for(OpObject o : cp.createdObjects) { + this.createdObjects.add(new OpObject(o, copyCacheFields)); + } + for(OpObject o : cp.editedObjects) { + this.editedObjects.add(new OpObject(o, copyCacheFields)); + } + } + + public String getOperationType() { + return type; + } + + public void setType(String name) { + checkNotImmutable(); + type = name; + updateObjectsRef(); + } + + protected void updateObjectsRef() { + for(OpObject o : createdObjects) { + o.setParentOp(this); + } + for(OpObject o : editedObjects) { + o.setParentOp(this); + } + } + + public String getType() { + return type; + } + + public OpOperation makeImmutable() { + isImmutable = true; + for(OpObject o : createdObjects) { + o.makeImmutable(); + } + for(OpObject o : editedObjects) { + o.makeImmutable(); + } + return this; + } + + public void setSignedBy(String value) { + putStringValue(F_SIGNED_BY, value); + } + + public void addOtherSignedBy(String value) { + super.addOrSetStringValue(F_SIGNED_BY, value); + } + + public List getSignedBy() { + return getStringList(F_SIGNED_BY); + } + + public String getHash() { + return getStringValue(F_HASH); + } + + public String getRawHash() { + String rw = getStringValue(F_HASH); + // drop algorithm and everything else + if(rw != null) { + rw = rw.substring(rw.lastIndexOf(':') + 1); + } + return rw; + } + + public List getSignatureList() { + return getStringList(F_SIGNATURE); + } + + public Map> getRef() { + return getMapStringList(F_REF); + } + + @SuppressWarnings("unchecked") + public List> getDeleted() { + List> l = (List>) fields.get(F_DELETE); + if(l == null) { + return Collections.emptyList(); + } + return l; + } + + public boolean hasDeleted() { + return getDeleted().size() > 0; + } + + public void addDeleted(List id) { + if(!fields.containsKey(F_DELETE)) { + ArrayList> lst = new ArrayList<>(); + lst.add(id); + putObjectValue(F_DELETE, lst); + } else { + getDeleted().add(id); + } + } + + public List getCreated() { + return createdObjects; + } + + public void addCreated(OpObject o) { + checkNotImmutable(); + createdObjects.add(o); + if(type != null) { + o.setParentOp(this); + } + } + + public boolean hasCreated() { + return createdObjects.size() > 0; + } + + public void addEdited(OpObject o) { + checkNotImmutable(); + editedObjects.add(o); + if (type != null) { + o.setParentOp(this); + } + } + + public List getEdited() { + return editedObjects; + } + + public boolean hasEdited() { + return editedObjects.size() > 0; + } + + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((createdObjects == null) ? 0 : createdObjects.hashCode()); + result = prime * result + ((editedObjects == null) ? 0 : editedObjects.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + OpOperation other = (OpOperation) obj; + if (createdObjects == null) { + if (other.createdObjects != null) + return false; + } else if (!createdObjects.equals(other.createdObjects)) + return false; + if (editedObjects == null) { + if (other.editedObjects != null) + return false; + } else if (!editedObjects.equals(other.editedObjects)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + + + + public static class OpOperationBeanAdapter implements JsonDeserializer, + JsonSerializer { + + // plain serialization to calculate hash + private boolean excludeHashAndSignature; + private boolean fullOutput; + + public OpOperationBeanAdapter(boolean fullOutput, boolean excludeHashAndSignature) { + this.excludeHashAndSignature = excludeHashAndSignature; + this.fullOutput = fullOutput; + } + + public OpOperationBeanAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + this.excludeHashAndSignature = false; + } + + @Override + public OpOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject jsonObj = json.getAsJsonObject(); + OpOperation op = new OpOperation(); + JsonElement tp = jsonObj.remove(F_TYPE); + if(tp != null) { + String opType = tp.getAsString(); + op.type = opType; + } else { + op.type = ""; + } + JsonElement createdObjs = jsonObj.remove(F_CREATE); + if(createdObjs != null) { + JsonArray ar = createdObjs.getAsJsonArray(); + for(int i = 0; i < ar.size(); i++) { + //op.addCreated(context.deserialize(ar.get(i), OpObject.class)); + } + } + + JsonElement editedObjs = jsonObj.remove(F_EDIT); + if (editedObjs != null) { + for (JsonElement editElem : editedObjs.getAsJsonArray()) { + //op.addEdited(context.deserialize(editElem, OpObject.class)); + } + } + + jsonObj.remove(F_EVAL); + op.fields = context.deserialize(jsonObj, TreeMap.class); + return op; + } + + @Override + public JsonElement serialize(OpOperation src, Type typeOfSrc, JsonSerializationContext context) { + TreeMap tm = new TreeMap<>(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + if(excludeHashAndSignature) { + tm.remove(F_SIGNATURE); + tm.remove(F_HASH); + } + tm.put(F_TYPE, src.type); + + if (src.hasEdited()) { + tm.put(F_EDIT, context.serialize(src.editedObjects)); + } + + if(src.hasCreated()) { + tm.put(F_CREATE, context.serialize(src.createdObjects)); + } + return context.serialize(tm); + } + } + +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java new file mode 100644 index 0000000000..a6abf266d6 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java @@ -0,0 +1,171 @@ +package net.osmand.plus.osmedit.utils.ops; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class PerformanceMetrics { + private static final PerformanceMetrics inst = new PerformanceMetrics(); + public static final int METRICS_COUNT = 2; + public static PerformanceMetrics i() { + return inst; + } + private final Map metrics = new ConcurrentHashMap(); + private final AtomicInteger ids = new AtomicInteger(); + private final PerformanceMetric DISABLED = new PerformanceMetric(-1, ""); + private boolean enabled = true; + private PerformanceMetric overhead; + + private PerformanceMetrics() { + overhead = getByKey("_overhead"); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Map getMetrics() { + return metrics; + } + + public PerformanceMetric getMetric(String prefix, String key) { + if(!enabled) { + return DISABLED; + } + return getMetric(prefix + "." + key); + } + + public void reset(int c) { + for(PerformanceMetric p : metrics.values()) { + p.reset(c); + } + } + + + public PerformanceMetric getMetric(String key) { + if(!enabled) { + return DISABLED; + } + long s = System.nanoTime(); + PerformanceMetric pm = getByKey(key); + overhead.capture(System.nanoTime() - s); + return pm; + } + + private PerformanceMetric getByKey(String key) { + PerformanceMetric pm = metrics.get(key); + if(pm == null) { + pm = new PerformanceMetric(ids.incrementAndGet(), key); + metrics.put(key, pm); + } + return pm; + } + + + public final class PerformanceMetric { + final String name; + final int id; + String description; + AtomicInteger invocations = new AtomicInteger(); + AtomicLong totalDuration = new AtomicLong(); + AtomicInteger invocationsA = new AtomicInteger(); + AtomicLong totalDurationA = new AtomicLong(); + AtomicInteger invocationsB = new AtomicInteger(); + AtomicLong totalDurationB = new AtomicLong(); + + + + private PerformanceMetric(int id, String name) { + this.id = id; + this.name = name; + } + + public Metric start() { + if(id == -1) { + return Metric.EMPTY; + } + return new Metric(this); + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void reset(int c) { + if(c == 1) { + invocationsA.set(0); + totalDurationA.set(0); + } else if(c == 2) { + totalDurationB.set(0); + invocationsB.set(0); + } + } + + public int getInvocations(int c) { + if(c == 1) { + return invocationsA.get(); + } else if(c == 2) { + return invocationsB.get(); + } + return invocations.get(); + } + + public long getDuration(int c) { + if(c == 1) { + return totalDurationA.get(); + } else if(c == 2) { + return totalDurationB.get(); + } + return totalDuration.get(); + } + + public int getId() { + return id; + } + + long capture(long d) { + invocations.incrementAndGet(); + totalDuration.addAndGet(d); + invocationsA.incrementAndGet(); + totalDurationA.addAndGet(d); + invocationsB.incrementAndGet(); + totalDurationB.addAndGet(d); + return d; + } + } + + public static final class Metric { + long start; + PerformanceMetric m; + boolean e; + public static final Metric EMPTY = new Metric(true); + private Metric(PerformanceMetric m) { + start = System.nanoTime(); + this.m = m; + } + + private Metric(boolean empty) { + e = empty; + } + + public long capture() { + if(e) { + return 0; + } + return m.capture(System.nanoTime() - start); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java new file mode 100644 index 0000000000..fb769b10c5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java @@ -0,0 +1,25 @@ +package net.osmand.plus.osmedit.utils.util; + +public interface DBConstants { + + public static final String SCHEMA_NAME = "public"; + + // Tables + public static final String BLOCK_TABLE = "blocks"; + + public static final String LOGINS_TABLE = "logins"; + + public static final String USERS_TABLE = "users"; + + public static final String QUEUE_TABLE = "queue"; + + public static final String ROLES_TABLE = "roles"; + + public static final String TABLES_TABLE = "tables"; + + public static final String OP_DEFINITIONS_TABLE = "op_definitions"; + + public static final String EXECUTED_OPERATIONS_TABLE = "operations"; + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java new file mode 100644 index 0000000000..e8ccc187f1 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java @@ -0,0 +1,275 @@ +package net.osmand.plus.osmedit.utils.util; + +import java.util.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Class uses for work with Json Object represent as Map. + */ +public class JsonObjectUtils { + + + private static final int GET_OPERATION = 0; + private static final int SET_OPERATION = 1; + private static final int DELETE_OPERATION = 2; + protected static final Log LOGGER = LogFactory.getLog(JsonObjectUtils.class); + + private static class OperationAccess { + private final int operation; + private final Object value; + + private OperationAccess(int op, Object v) { + this.operation = op; + this.value = v; + } + + } + + /** + * Retrieve value from jsonMap by field sequence. + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonMap, String[] fieldSequence) { + return accessField(jsonMap, fieldSequence, new OperationAccess(GET_OPERATION, null)); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonMap, List fieldSequence, Object field) { + return setField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), field); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonObject, String[] fieldSequence, Object field) { + return accessField(jsonObject, fieldSequence, new OperationAccess(SET_OPERATION, field)); + } + + + /** + * Retrieve value from jsonMap by field sequence. + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonObject, List fieldSequence) { + return getField(jsonObject, fieldSequence.toArray(new String[fieldSequence.size()])); + } + + /** + * Delete field value from json Map (field path presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return + */ + public static Object deleteField(Map jsonMap, List fieldSequence) { + return accessField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), new OperationAccess(DELETE_OPERATION, null)); + } + + + @SuppressWarnings("unchecked") + private static Object accessField(Map jsonObject, String[] fieldSequence, OperationAccess op) { + if (fieldSequence == null || fieldSequence.length == 0) { + throw new IllegalArgumentException("Field sequence is empty. Set value to root not possible."); + } + String fieldName = null; + Map jsonObjLocal = jsonObject; + List jsonListLocal = null; + int indexToAccess = -1; + for(int i = 0; i < fieldSequence.length; i++) { + boolean last = i == fieldSequence.length - 1; + fieldName = fieldSequence[i]; + int indOpArray = -1; + for(int ic = 0; ic < fieldName.length(); ) { + if(ic > 0 && (fieldName.charAt(ic) == '[' || fieldName.charAt(ic) == ']') && + fieldName.charAt(ic - 1) == '\\') { + // replace '\[' with '[' + fieldName = fieldName.substring(0, ic - 1) + fieldName.substring(ic); + } else if(fieldName.charAt(ic) == '[') { + indOpArray = ic; + break; + } else { + ic++; + } + } + jsonListLocal = null; // reset + if(indOpArray == -1) { + if(!last) { + Map fieldAccess = (Map) jsonObjLocal.get(fieldName); + if(fieldAccess == null) { + if(op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + Map newJsonMap = new TreeMap<>(); + jsonObjLocal.put(fieldName, newJsonMap); + jsonObjLocal = newJsonMap; + } else { + jsonObjLocal = fieldAccess; + } + } + } else { + String arrayFieldName = fieldName.substring(0, indOpArray); + if(arrayFieldName.contains("]")) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + jsonListLocal = (List) jsonObjLocal.get(arrayFieldName); + if (jsonListLocal == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + jsonListLocal = new ArrayList(); + jsonObjLocal.put(arrayFieldName, jsonListLocal); + } + while (indOpArray != -1) { + fieldName = fieldName.substring(indOpArray + 1); + int indClArray = fieldName.indexOf("]"); + if (indClArray == -1) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + if(indClArray == fieldName.length() - 1) { + indOpArray = -1; + } else if(fieldName.charAt(indClArray + 1) == '[') { + indOpArray = indClArray + 1; + } else { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + int index = Integer.parseInt(fieldName.substring(0, indClArray)); + if (last && indOpArray == -1) { + indexToAccess = index; + } else { + Object obj = null; + if (index < jsonListLocal.size() && index >= 0) { + obj = jsonListLocal.get(index); + } else if (op.operation == SET_OPERATION && (index == -1 || index == jsonListLocal.size())) { + index = jsonListLocal.size(); + jsonListLocal.add(null); + } else { + throw new IllegalArgumentException( + String.format("Illegal access to array at position %d", index)); + } + + if (obj == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + if (indOpArray == -1) { + obj = new TreeMap<>(); + } else { + obj = new ArrayList(); + } + jsonListLocal.set(index, obj); + } + if(indOpArray != -1) { + jsonListLocal = (List) obj; + } else { + jsonObjLocal = (Map) obj; + jsonListLocal = null; + } + } + } + + } + } + if(jsonListLocal != null) { + return accessListField(op, jsonListLocal, indexToAccess); + } else { + return accessObjField(op, jsonObjLocal, fieldName); + } + } + + private static Object accessObjField(OperationAccess op, Map jsonObjLocal, String fieldName) { + Object prevValue; + if (op.operation == DELETE_OPERATION) { + prevValue = jsonObjLocal.remove(fieldName); + } else if (op.operation == SET_OPERATION) { + prevValue = jsonObjLocal.put(fieldName, op.value); + } else { + prevValue = jsonObjLocal.get(fieldName); + } + return prevValue; + } + + private static Object accessListField(OperationAccess op, List jsonListLocal, int indexToAccess) { + Object prevValue; + int lastIndex = indexToAccess; + if (op.operation == DELETE_OPERATION) { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.remove(lastIndex); + } + } else if (op.operation == SET_OPERATION) { + if (lastIndex == jsonListLocal.size() || lastIndex == -1) { + prevValue = null; + jsonListLocal.add(op.value); + } else if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + throw new IllegalArgumentException(String.format("Illegal access to %d position in array with size %d", + lastIndex, jsonListLocal.size())); + } else { + prevValue = jsonListLocal.set(lastIndex, op.value); + } + } else { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.get(lastIndex); + } + } + return prevValue; + } + + @SuppressWarnings("unchecked") + public static List getIndexObjectByField(Object obj, List field, List res) { + if(obj == null) { + return res; + } + if(field.size() == 0) { + if(res == null) { + res = new ArrayList(); + } + res.add(obj); + return res; + } + if (obj instanceof Map) { + String fieldFirst = field.get(0); + Object value = ((Map) obj).get(fieldFirst); + return getIndexObjectByField(value, field.subList(1, field.size()), res); + } else if(obj instanceof Collection) { + for(Object o : ((Collection)obj)) { + res = getIndexObjectByField(o, field, res); + } + } else { + // we need extract but there no field + LOGGER.warn(String.format("Can't access field %s for object %s", field, obj)); + } + return res; + } + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java new file mode 100644 index 0000000000..50ed65cba8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java @@ -0,0 +1,122 @@ +package net.osmand.plus.osmedit.utils.util; + +import java.util.List; + +public class OUtils { + + public static boolean isEmpty(String s) { + return s == null || s.trim().length() == 0; + } + + public static boolean validateSqlIdentifier(String id, StringBuilder errorMessage, String field, String action) { + if(isEmpty(id)) { + errorMessage.append(String.format("Field '%s' is not specified which is necessary to %s", field, action)); + return false; + } + if(!isValidJavaIdentifier(id)) { + errorMessage.append(String.format("Value '%s' is not valid for %s to %s", id, field, action)); + return false; + } + return true; + } + + public static long combine(int x1, int x2) { + long l = Integer.toUnsignedLong(x1); + l = (l << 32) | Integer.toUnsignedLong(x2); + return l; + } + + public static int first(long l) { + long s = Integer.MAX_VALUE; + int t = (int) ((l & (s << 32)) >> 32); + if(l < 0) { + t = -t; + } + return t; + } + + public static int second(long l) { + int t = (int) (l & Integer.MAX_VALUE); + if ((l & 0x80000000l) != 0) { + t = -t; + } + return t; + } + + public static boolean isValidJavaIdentifier(String s) { + if (s.isEmpty()) { + return false; + } + if (!Character.isJavaIdentifierStart(s.charAt(0))) { + return false; + } + for (int i = 1; i < s.length(); i++) { + if (!Character.isJavaIdentifierPart(s.charAt(i))) { + return false; + } + } + return true; + } + + public static boolean equals(List s1, List s2) { + if(s1 == null || s1.size() == 0) { + return s2 == null || s2.size() == 0; + } + if(s2 == null || s1.size() != s2.size()) { + return false; + } + for(int i = 0; i < s1.size(); i++) { + Object o1 = s1.get(i); + Object o2 = s2.get(i); + if(o1 == null) { + if(o2 != null) { + return false; + } + } else { + if(!o1.equals(o2)) { + return false; + } + } + } + return true; + } + public static boolean equals(Object s1, Object s2) { + if(s1 == null || s2 == null) { + return s1 == s2; + } + if(s1 instanceof Number && s2 instanceof Number) { + return ((Number) s1).longValue() == ((Number) s2).longValue() && + ((Number) s1).doubleValue() == ((Number) s2).doubleValue(); + } + return s1.equals(s2); + } + + public static boolean equalsStringValue(Object s1, Object s2) { + if(s1 == null || s2 == null) { + return s1 == s2; + } + return s1.toString().equals(s2.toString()); + } + + public static long parseLongSilently(String input, long def) { + if (input != null && input.length() > 0) { + try { + return Long.parseLong(input); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public static int parseIntSilently(String input, int def) { + if (input != null && input.length() > 0) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java new file mode 100644 index 0000000000..8a5d4504e3 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java @@ -0,0 +1,15 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class ConnectionException extends TechnicalException { + + private static final long serialVersionUID = 8191498058841215578L; + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(String message) { + super(message); + } + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java new file mode 100644 index 0000000000..eba54a8260 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java @@ -0,0 +1,16 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class FailedVerificationException extends Exception { + + private static final long serialVersionUID = -4936205097177668159L; + + + public FailedVerificationException(Exception e) { + super(e); + } + + + public FailedVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java new file mode 100644 index 0000000000..70df91a4e8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java @@ -0,0 +1,15 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class TechnicalException extends RuntimeException { + + private static final long serialVersionUID = 9201898433665734132L; + + public TechnicalException(String message, Throwable cause) { + super(message, cause); + } + + public TechnicalException(String message) { + super(message); + } + +} From 76b859afc0fa08655bca5ba68eb867e73eb59552 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 15 Oct 2020 12:36:42 +0300 Subject: [PATCH 072/123] Fix algorithm for compiling an ordered list of RoadSegmentResult --- .../MeasurementEditingContext.java | 80 +++++-------------- 1 file changed, 18 insertions(+), 62 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java index 66ced9ebd1..307d89d501 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java @@ -314,71 +314,17 @@ public class MeasurementEditingContext { } public List getAllRouteSegments() { - // prepare data for sorting - List fullList = new ArrayList<>(); - for (Map.Entry, RoadSegmentData> entry : roadSegmentData.entrySet()) { - fullList.add(new TmpRouteSegmentData( - entry.getKey().first, - entry.getKey().second, - entry.getValue().getSegments())); - } - // sorting data by connecting together - while (fullList.size() > 1) { - TmpRouteSegmentData firstInList = fullList.get(0); - for (int i = 1; i < fullList.size(); i++) { - TmpRouteSegmentData other = fullList.get(i); - boolean isMatched = false; - - if (firstInList.isAfterOf(other)) { - isMatched = true; - firstInList.joinBefore(other); - } else if (firstInList.isBeforeOf(other)) { - isMatched = true; - firstInList.joinAfter(other); - } - - if (isMatched) { - fullList.remove(other); - break; + List allSegments = new ArrayList<>(); + for (Pair key : getOrderedRoadSegmentDataKeys()) { + RoadSegmentData data = roadSegmentData.get(key); + if (data != null) { + List segments = data.getSegments(); + if (segments != null) { + allSegments.addAll(segments); } } } - return fullList.size() > 0 ? fullList.get(0).getRouteSegments() : null; - } - - private static class TmpRouteSegmentData { - private WptPt start; - private WptPt end; - private List routeSegments; - - public TmpRouteSegmentData(WptPt start, WptPt end, - List routeSegments) { - this.start = start; - this.end = end; - this.routeSegments = new ArrayList<>(routeSegments); - } - - boolean isAfterOf(TmpRouteSegmentData other) { - return Algorithms.objectEquals(this.start, other.end); - } - - boolean isBeforeOf(TmpRouteSegmentData other) { - return Algorithms.objectEquals(this.end, other.start); - } - - void joinAfter(TmpRouteSegmentData other) { - end = other.end; - routeSegments.addAll(other.routeSegments); - } - - void joinBefore(TmpRouteSegmentData other) { - start = other.start; - routeSegments.addAll(0, other.routeSegments); - } - - public List getRouteSegments() { - return routeSegments; - } + return allSegments.size() > 0 ? allSegments : null; } void splitSegments(int position) { @@ -501,6 +447,16 @@ public class MeasurementEditingContext { return res; } + private List> getOrderedRoadSegmentDataKeys() { + List> keys = new ArrayList<>(); + for (List points : Arrays.asList(before.points, after.points)) { + for (int i = 0; i < points.size() - 1; i++) { + keys.add(new Pair<>(points.get(i), points.get(i + 1))); + } + } + return keys; + } + private void recreateCacheForSnap(TrkSegment cache, TrkSegment original, boolean calculateIfNeeded) { boolean hasDefaultModeOnly = true; if (original.points.size() > 1) { From 4cb48a38a99af68db39f755283638ee518317dce Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 15 Oct 2020 12:40:16 +0300 Subject: [PATCH 073/123] Revert "bug fix" This reverts commit 93db8c4c --- .../java/net/osmand/osm/io/NetworkUtils.java | 49 -- OsmAnd/libs/opendb-core.jar | Bin 350613 -> 0 bytes .../plus/mapcontextmenu/MenuBuilder.java | 89 +-- .../plus/osmedit/OpenstreetmapRemoteUtil.java | 78 +-- .../utils/FailedVerificationException.java | 16 - .../plus/osmedit/utils/JsonFormatter.java | 135 ----- .../osmand/plus/osmedit/utils/SecUtils.java | 411 -------------- .../plus/osmedit/utils/ops/OpObject.java | 530 ------------------ .../plus/osmedit/utils/ops/OpOperation.java | 281 ---------- .../osmedit/utils/ops/PerformanceMetrics.java | 171 ------ .../plus/osmedit/utils/util/DBConstants.java | 25 - .../osmedit/utils/util/JsonObjectUtils.java | 275 --------- .../plus/osmedit/utils/util/OUtils.java | 122 ---- .../util/exception/ConnectionException.java | 15 - .../FailedVerificationException.java | 16 - .../util/exception/TechnicalException.java | 15 - 16 files changed, 28 insertions(+), 2200 deletions(-) delete mode 100644 OsmAnd/libs/opendb-core.jar delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java 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 1590116f5c..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 @@ -56,55 +56,6 @@ public class NetworkUtils { return e.getMessage(); } } - - public static String sendPostDataRequest(String urlText, InputStream data){ - try { - log.info("POST : " + urlText); - HttpURLConnection conn = getHttpURLConnection(urlText); - conn.setDoInput(true); - conn.setDoOutput(false); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Accept","*/*"); - conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$ - conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); - OutputStream ous = conn.getOutputStream(); - ous.write(("--" + BOUNDARY + "\r\n").getBytes()); - ous.write(("content-disposition: form-data; name=\"" + "file" + "\"; filename=\"" + "image1" + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ - ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$ - Algorithms.streamCopy(data,ous); - ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ - ous.flush(); - log.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage()); - if(conn.getResponseCode() != 200){ - return conn.getResponseMessage(); - } - StringBuilder responseBody = new StringBuilder(); - InputStream is = conn.getInputStream(); - responseBody.setLength(0); - if (is != null) { - BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); //$NON-NLS-1$ - String s; - boolean first = true; - while ((s = in.readLine()) != null) { - if(first){ - first = false; - } else { - responseBody.append("\n"); //$NON-NLS-1$ - } - responseBody.append(s); - } - is.close(); - } - Algorithms.closeStream(is); - Algorithms.closeStream(data); - Algorithms.closeStream(ous); - return responseBody.toString(); - } catch (IOException e) { - log.error(e.getMessage(), e); - return e.getMessage(); - } - } - private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$ public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, OsmOAuthAuthorizationClient client, diff --git a/OsmAnd/libs/opendb-core.jar b/OsmAnd/libs/opendb-core.jar deleted file mode 100644 index ac14553f6d290172c9314326bb8a192590613b92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 350613 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 Um^KauND|yZujLwOSj~ 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

SpD9a-U z&_fE$BT+lctgf4w1I1P0R%RkeH>~v-t=G^+sXFb1D*lNjXtT_iuE7z|J54>#ri8EE z%x92}>ewdCG{&!N*RZ3TJ${)xF}0aX6s${ zbhpTbdV;nK96d0Iw?ZJ3!%`td_i2UU%s)v5H+Ls{MoRCC zdR*ByXr3|my<3b~h_4xGZ$$NzEOwu6qyX6TL|bS<9NXSkq<{fmr#0Uv7JCn+@@rh})TxM1GG0Thi5FAc z@0UM=^K4XrCsZ(C^$JsTLrZS52-V2cM9s^ulQKbT%;;uKNGg$y4BSyv@u(BcyG>oY z%N2h-WtA4B#8KFm5^wgYnB3d0`U?)rl?F?*!I3 z7!c6~#u|tW_a?4**^fB|2~H*MkW3J?-xsB*Lpx?AW^dDolhl~@N&1s8>;mrKqChv0 zkvjY=$Vp-9AZ)BKbI#!XlND!Glz)X{J5^BAc@OfweJOT=2*qpe zJJ|Y$yUQ4QyAJc!S2G-IO*vC<_BFI|C*OkhYm|B4n2Fkra_=#B)@3HaAtOmnfhoA+ zpR6J3!I-*ysMZv>13uQ-iYQOClv#&WO(Nc|G)7UdF(>zaWb9TDbi`u<2+>Uw+Y(cT zGs4A1P42=i>5{MnrSb)s8~A+67$yelR`>o`@e6$3+ZK9q7<;_y@C{bF=xBT;ub8H~v1= z<}AK)?qi(0nWO38<;M%hhA_YSzIN_@@9cK(_IN+OqXSrv-a6Hzm?428$!xx z!E_s)ArDeAK7m_s(B{2@)C1qb9uRiP3&2aell&FRiog5e$r=P7ngM^b=jS6yiG@1r z3LXul=s!ftfglQo2X{cjhdd~JOUv_6g27L{3n!r4OAOpoM&iRA(()ENbJl}FAGW82 z&mI2v>(tME;5+fo15)3BBD_?2%UT}G@-^qhovM6&OT~K7Bo5mpJM&p`xS?$rp{mlR zy!SQ0-B=qK*ve$Amfd+$&H1w|LB@&x-88G;80ox)Z1C!@CZ+Nsw_KVO{4Mj;jfQ%G zZnWmP$^^r8n#GgmR?M(GSuBQ+gRMH_5y^&?YO?mxJUtVN>E-bOCLBjpG|H;7r06l( z?Xr9BF#*ywV`OG`#)PrgMX;Gp>%xkls5)00X3lgJh}3o$e@*rYNcxr(xlMw?)>82k zRee)6sj+Ov&NQ-Y25Hk|v=wfLQK>B5K2uPyVC%GR=%dquy15}dRe7DT4sY#)*hB=W zd)3aFB&G$gga51gxB7>0x$w2FPIIN@S#G~HHcAGYV{tzDXE-P(N*y;VwN8JXQ$P># zNPwm6>J?UK{g_ZXjnN*~CR&_cW(?EYMKlO#XWYS1<|6O)mbKzWVlbs8lM1Pa3L|U% z41v#p156mY@MBd6(hUo4c3BaYTse=am9xq~D&~Vs4T(t2pMt%vcv%#wl6b0${7oTQ z^2&sW9mL4Z0Z&Bd!tO}Ij}fO34OeEBEn6lf3v$VF(#41Ea+q4GBvx?TOc%>eO+H5xw%Kc3oq{Aa7VTuMy>F{&NpP|d^?Q}22 zla%Ub3N|--uZqfV=l_eecMh`U?YacZb<4JG+tw}Hw(Y80wySR0wr$(CZChQxcix`< z`sD zl-uQDbCjvxmw%xOjTQ`JBEhuu)p(cRiSrSa8Mpj18MlHVF`P>hYhl)nvHSEf+6V8> zUy1_D8M}h07`vj1F_Jd-lr7VKcaN6=PK&!G<+|I`{f5|%yUmy}5htgo4o1*QUIES%K5H}yxs+cpc{MP{e!DTgj=0(qliJb+ZLI2%WN%aoj212OhqQs+s*7ls6XBIaMRWeOy_uF#24 zru>_NbE%}#o3v8aI!f=tbSUP?^U*=}#w#A{G}xmHuM}xO=9`T()ugS09S&SAJQyD+ z1u@r+1F9$qwNU4|MuZ=0Q6kwzE&cOmUOPjIHty@`bW<{Q!XIgH&81_|LVr3EA zq?sRoUHq>i_eO5pIQo;sDD2b_R(;3^YwTe?k7{BCU7Y3bc-qTfw|OoEbtURCH*6Nt z85IZ#jvvm;+|vY$?@`bW&kHytMzdY?6%n?cSOVH>k&X3MBC#ukp;16=3$wp)H-zw< z;bEnsKqp1=n(dYrtl_mAfx9z#&*-pQWF{n&LS2HE0N*fcl5gIBwEnuIqaNOd@7uOV z`|_jz3M2m#S^LI{5|-Z|>-~cbfGvK~k1ejYXOFKRvFsPP#0zdg==sSmUK4YY9u~hw zrs^XLZl4IyMEQn&N85LeMT~C;b4P!tU7`maRLMc~kUrLK`N#pM8GbwN7WCvGCO=>Y zF(_Y0Gp)+xY^Q#zw@RL*n6&U0QK2nz(b&IB$-@qrihBVQ#nbL0BN;~O!yKBVa;pxZ zqcoM=fi?~=Z00yt`Az66Iq*fU+!lliTu$8c0#haEqhC$?6pO!hWgMIQQEt1jZVv4Z zB5->bKc|QM>Q2t`;mA^rDY6~n5U+o2=_CoBL7Oe|rq(a4PO}Uyr-aeY5(Bd11cx|; zRtN_w9yt9rEk!+cbZYH5@P-)_+AR^ALq@+tV1bR|lC&l|uRe=mAO&1`B4Tqw%!@TA z?8pR_A1c9)Z@gC`?LBlGgLGd$DF&*-S7T8Sx|XD6D*VQ@33{Jes*a65%$oQVOO0WY z0M%Y$|5xFR{O>nc_TFy(HEe+l!7u7c4Ldw@cd$=k`^cr`QV^{T&JToZVXDFbj}Jg= zrzIjpf&{(c(**PBLJu^7Ytjjsk=APcL^&=8U_`9pwDc9~+(0Xz9pAv2@|#V51VE&} zPDV=#ITC#Vpz5c?IXOIvyui#6PBdut7(;K`<#YyNiuj75lH`<*Xjr1Pv%uTd=zvpn?~;f9tG6>n>L&T-b3n_J5~RW;v9I$C7fa0LTRGrPeKPa>doE z`X7TaP>*du;sjm=CdvO{svH1>^Ykc70|x3Z!_KAUk`*l(&yBF#6rXFEMmV%L^BS)` zJ%h$=uXlT4Ml-06#6fe|;!^zHJ>$>1=Q$bZy%p;rfGMApUeg{;M63f8FRuF;&6Y zM$t*%$??C#oZGHQBFJAw677>Nf(Z)9@Q132%s{0p{%Z;nlI6V(i$zKKw&|DXs0o|& z?Gy`S-!ORIdm__uj_|@$w>J|$dwc|5_3FQW@dnM$ucmrlZ(P4$ZKQI4USInFOzgEq zahq66=}q;CU{OpMo3$G4BYANKPt)51p)$s{SS3Z6ylnKLq$hFIlc%r7r_yNtVCaK= zW&AGrl?_Ple!90lzd(!zpA&U#IQ2^ep>%&msss#$R`3avXALwRhpqZ~CK#|n3G7aRhlv1`oTi5^`!6;Q8-AY#t=fdU; zkA$Q!iyh8MYcWrgO!WC#n3w@N*nxl@LmM&6LorPq$wBBU`C2r_zUL27tJkbSla}}W zw0RjhxE2kLI1SZiXl3N-XajSHv`hbG`0X32NTdcyUH|axBAW2|^ z^LEjIQcw5!G2ly|N5Lnv^2?w&i(7-IhSRiVtF$b<>-GqsB($oAH3W0DHxXc{CR@&j zKW8p%@AgvVDmCLWvjh>WqoNmdB%b3AE-q!!5bR2Bo3!BK1am$HFFM%X;rWx*81r5qGLEF} zw<&$^wV5!P2b`#4K&RdGcb<{fPcZ|x``Az^GK9+pIz794-q3vhBsWkM3)(A#;OLz( z6Sfb99xiwn0=yacXs>*cxf^8?j|5Ysr9MZZLi>Vjjnc2(3fdj$NG*S!SG!Ga(c^#D z$Jpup0($@)y1oqP8XV|ZA3?kz{|=|#75nST`82qSglOUddmLtcQgF)E$6;&Pccehb zyiO8}X`xd)-#+K_U?i#j$n~nBn7X5 zJIF|!CMI7sn)^S+g%>&7MC+fxpz_n(|F43Q|M_($7B#mqcQpIw)pq{(yIrYhZ9Dgm z)hs!3+m%T0eCG>eCZv>Vv6M9ac&vb+AghqR3Bw=q%|vI4%Q@5!&1jM-Gv@b;7{;u1 zg#Nv~5DAH?QO9Y=>HCcFPCg&+PeA+-RS5AB@(}}(p^8W{NYYwLf7Ic>f+EuNSwjp& zcN*9faoMA8Q}E@;<&Af!0UEW~<9su~L?>?`<bfVC<*HJ_(;7=$qtg_M*F$__f)N0=(Nt(`OK z&?A3qstf|@l(j^VpeTx$r63Rl5%(VqVqoMIU2+;xCr&|6cF%JjnD8GtsxtJ~@P3E| zPE-1h^41!d99Z2Zi zHsYy(xBsTo5CicEa`7_V0T~vGbuU4!#}Jm@lGuX}$%t&I_B#*wo?%0VFj|4^YNx70J!v-!`WXp* z5V*E0e%I37E2ZF3oLbxK5S>y28zsd8aaeDl2C=_PaKcqH~T}2#uQd#mDGddT3n$0kq(u`xAF~dsQRa@Q7 za=Q=8eO+C8-MPPP`5}B4J-z65;1OrhVT~n2y568*yy>{e)T(->COqq^m5A$QJ~^dz zo{0_m4JX@_zp_ZP{ItWml94~bmt&rBlO=3M#Y-_3(+1k|VM2r(aU%mI?eqtQYM?L0W&v&edO9fkLx0!1_T!344ae6d<#7_p)Z^li{mQ&w8|4hVL!rRufu9&PTZ{7i-h*ewq}kG~l_#$_ zzs8Xiv8s!L^B&#Z`T@p_m@MkVIDA5kz7)NRYV=22iOuq7lD@U37c${{ zYOt#PbkGBxkB3xjy-EyVs%y>zk!lU5xDSbh`SZq+UpJEn}DTE9KUl(-HW1q|K@eKZ72mKw%{ znOUf_iW<(UaiTr;r+LD{#CbNaev|vk|{oV>9>YC7K4F`vAV9l}Ugd?6sDL zbg1H&0+!?*B(E{`T_!Kw9`6_zKHh{k&`X~@u16r8@%2#aT<<+N=3 z9$OHPQuZv|mkBt2rWA-x^`gy|34h@L3Pxz50rp|4kAa0eAz_nsF5IAcJUezw-Ga3z zkPW+!nGq-y<<>|EbxHlT4U-ErMO6)`0Of`1r+pHAhrM06#h14fMs8k704k+>g#b%~ z>I2c{t+*@y=~2L%3VFq429l{AH%Hs%+xnPa$-TM~wS>X5A?#2lUE{Y9f%k5bZ3KM# zwg=MzvnN<2mmpkqb(iktE8X^>z75_34JQpZ-!{=L;yt4r5=7jE3et$xMFp;k#v1zc z#>hhc3Ot*vk7W(l+oLMr`v4OfY_)}RG1xx zBuWG~yzGRPJsFS0ZrC;Ijj+BZ8eJ^19)@&f>DxK@1Td?kV(KNlvh<}VV+AhD-wD+! ztXp@HVQ^ z4a}9h_rntLcQKJvP=>6d?6cVM5DaUh+Y+nQ&({hKEPKx)|63Rh>8Q|M6-2eRII9O7 z6Rjs)@H`QS&UkQX-RV@$pdSs64Tn*IqOO?bc9o7@CtYI*HYiGmyE>AWkb7LwfZm36 zq-wMnqJs3aGDKWtC1p4yipI%dV$I#F7|nt8gZbxU@%$kCiH!2RGA^wh6x^FqO0j}| z-=}gp7v_gp8?sx-iJDU-+nMkA`*Hq5Yw|4WLu{>Oq4YxEwYu{#15*?lL4fSxA zc*ib!KDjL*Z{{6-$WfYs$oNs#1_*N)@h?hoA${zGTKI4w+#X?rB3gOk_wiao0Px=! zP1IlQqV_a_5C&6!5cvIx`t1|u=YiH*QDU0y6HOuv7$1hT1m4*GXLNsiQ#kw!1pr{5 z^}kMZ`ByQlY-gnJWGrE0Wb9___)|Ol8!pg*@>2ZArhbn2nYFc0jvCX6mR$Ta;Kkp( zOb-uV#4HXK-Ybr@*_v3|AYNhSbtR*%G<`k8hI2C`eUG&{Hvu58M`wbAjqQ2v^k|Fj zIm@#J{L{zhcx!{yy2sz6pv${FHpv-Aqx%b|D-%BwSCI%*M-jk}+3^fN(|USpBmC<4^70!}lvIr?!}y~`tAmkU3{;RxqRx? zb<@Uw3>7pf!s`)Dmo)4_fP|p$!QwGCEFXPp_0|&e&VqY0%Q*vPupv7g(IVe9Fpb5Y zdz+KJeqHfj0Xo}w^bZvOo`bJvnszgK2#yAH1H@+%JKcFsJVlZV7HmUEZHu`6U8W#)F_ z{iNXDCT6$T{xjogs}gOzndDJQ3vl(RR2iS4Nxjq%N$r+d3`dHLumh>$ew|Vg-(P9d zz0t2ODWe%|8DrKT#{gt$gCB#&jVBaDoNKzmhP+OeWAq<@F}C;0+gd;*4{)CMic02Bi(DAm=5i3~WP_ zyA^4nkHK{&CWhP=!EKVWfeIDPT%3BaqOfrao|X`pZ7GvsDt`~}v_%_DY#5nnDsqe*gbxRPu-o(FVk1eJE={^C zLsNzT%*IK<9SSoHnv$c*1}2Hksj=hD(q6EY-Zz*;rKUm2a7LINFWzFYc zGJ&cPQG|dhW+c<89cqJtdnH9H?V!$5CY2@Aze5z<uWlOQiC`6( zY&d}aB~29tN#8(Zs8gCbjefnAms+(Yi7$Q+1mCg8!f=EUGN}Dh?PWX>0FmK~$bP9Xlk5LKq;!1V+z1tcap%#(>VH1G*&?L%s_Qe}>d+fk54+x?7Ih_NA z8*@827Sp(soe^Eu%0xraRV^)&WP#gMi`B8M>I^?1K1yr~g;OXY0G%g=;Iw$*V$&8Ye3;N?D-u2vBphXy$fy>W zgxkCy2+n?VD&*G=Agz^w$**YX;^6pN$B9WK20MwvOcvJsu@!fL`l0LP7htLRc?>3; zQN4VzSanY}Jq@DP69C1$D_>r>M?d;MY?U;obhj=Z0T5i-ZY4fX(X09@iFj*~&bT7e5A;fit4nnMdQYm=Qb)tWmj6|j;geP&zXi2|GacF`B zut<9r34Kn#xiwO%=!j0w5gIYKryk}2^DiIaPg&q@?%Ye>rUXX$u|>{)KU&egYdFlQ zfq$q~Z}xh;?U?|9&L(n}KH%?wG*dY<&MP~IG)i|s7Ss~r)icD0O5R1wyy#6SDJq*Srv{Uj%v-JwhL3}Phv32+ zx-T>Wj_Ge^zzT!4jb(|u2(_n1ne3Id<(e!`Qn)%itk*2*mmrUXl}RI6=uENNuSAF? z!;v`LW&svBLl=*QHF_Sx6&Oh9x>}G2uu{>Q7OvIeJBp|sEb2d~fNlP6#&~e?2oYfW zkq){6pH0ZYnm+I^y8~u717F=}b%g0cy75B0WHO`k?%jdYAHX|mXZZ^4eZKkedPm>3 zyz#EcPr$P@4`{yml)9`IK_YMH$uA>TgZ^rvwamG5gpz7olivPQ0Mg|kdEDbpY#=0*Yj zBR&n(zFJ>bAK+aoCFfVJDz3W+O_c}pCzQ|froUryhucdHZ%c~KTDBMgf^erZ%d5J&@saei*6Dd*4J=uG<;s;}r zlrR`zMyorvck^IAlDOMP*XR$xZ@InO#?3?9OfMLJk|4x5&VyM}&Y-56IJ-!>&P~Bh z2mJ}SnZLSvr*H2;g~(|%7*j_~R-a+M00SR?F8x!Zv7DX72=$-D z5NT4sas`-Du^ze0MzWc|>U({5lMi(0{eRCPpIc)_S}~r;KA5_#r;V#MPu2N~+XG3{ zfEDKA(x8Z%Z?y&(Ixe>vvAk4Yu)f%SxCVUp`hX9V#cR;CD3j<%V+SV%#5rN|hcb7l zdH=CZZ(D>tQg8`_EPJBFY9eO=E8JrS6N_6^vooDYLC}h$CsL=@R34M57H2d`Bvo{< z8G;7!C&H!lou8AUgkcWg6~;5+{H$pngV;;j%$=o+^p4ls9ZvA37_jg>|FN-Y`djVU!|S249aurGiShlN^sbw9g<${6sUiDGm;*K3BReL8TQ-+!Kz}& zHjx{o0le&=@NW}(4PQ0xjSM^ z@;w1Ota{rR0~b04<7hsm<~o}Y5^os9&T9AsygmVtSE5zG7!+>5Z0Y3aDIU!mXsdK* z-rz|d9$>rl#a&N4@h$j6><<(4`pj+LdkKC{MWKHGum991NigJuR zwmBE39VDBRHiimdGFZ!v@HnK!qZ+PCbk~TIjx$6>)W0cY&XnI}?TTVE1E;V(zFns2 zn(~NLEl^UYQtl7y}WPnF{#-0B`7Xdhu`luH}c-u$Rxpssta5`lUL%DHYs zD&;(Sz?Q4wS>8G4ebYeI8caQ2W7TKY`j^vo$4gap=mf`c?2l!sL!KFgjBv>^ctXW` zWR>8IQRM}4;L;>V>B{`r>&}PD>jYB3Tb3DNqB1QxkRzwCa=r;(WOeugZTchf{amg% zp{0E-4X3Apvq|QI#;AV)X?|bRC_{ed;@UO*XDHV%MNPHZZ^_6;DW((7wye;OgLp}T zNG%7tf7l2Y7Kx|Y7O-Sj_Kw%N&(Z1aL0fDlQ$5#}PFPvj!|RYeEjL`PUHV+D zXPfIKd0hrrt$0c+cs=#O8*Z%eAV1{#vf`mct3 zJ;|h9c8z~$Sv<`x)D#O8MawDb+x93bS^_JDH2S*YX=B{1#d>_wrZu^6;ix+*WdQ-g z#H5|N9h{oQ5${0o|24EJ9wHRer&dg&f{wxj~mv1z#pSm_Bqi4bQU zcc41pPlbs+iYdZaExvRx!tScHo)#;zGmw^W=X8fD0z_XN_~_e}AY%-v3Px#NOp_Q# z(tvF9TwEoS!z@FcABo$}NMYAI4n5=(9E%sBW&Bhk1NTNlqVLk=I0K%Yqo;53g zwrZobO`PNwN)kS3bzY?A~`W;k?me03xAf5_`5S_ZVBsiMkURkHo)csS`-Oz z1_Yl<8La{de+sVsHl5vH_{~?$`i9atuWOF5JhQ4LiNhU}bce_pqB_qQEl;K^qwU31 zxx-y~AXb|GEDz_CpmK|6-dOIx)DoR@ujSk@wa9r3E6BIdg&0hDXF!kRpNyo`J`h8T{BjGAyZ+`4 z68ru8joCZcNt_E}sU?Qe9apybL_W%rg6Ef3hMjih1H34$I8OktB%C3vb6*wd`i(1~ z@*3hXPS}0)!1}1AWHa!bI-~yLQTP0A45Fc<_rUJkud;bsyOxBqr+ElWY2|NR96$C% zOzmSSGo?x}#R|Y#zSAfo=8%m+e#Y}RwpSECoC7Mc9F0#;DS_{ZpB7M{Ob#UYUo((H zBu*`|m>`5Gm-7a(K|rX zSh*8nS1AT!&+1FtVulB@^t1D=Ye9X1iiOVh8`+z||3foz+0&4NYoF8QuWUbt>~LA^ zTNiv^49=(ZeiLi4AXnc} z#3ZZD*xXkJso$(@IhEKJPP`%348mO!x~XsL-Lt;Zkm(_(yv!?w0%%Qk7r$g%A8cT% z449bdX;g60!Kp>DW5WipX@O-2PI=!TYLV(Ww$NOBos;^=a;&bbapW?i@z)}f(#UA> zoQCL`%DgX&h6oGS>0>VZ7Xh&_lC@xKYb5IY;ev#1(V`P*rPxgIDJ3(gs!&$*Oy`42 z;1|n-bX!taRMQ?u12Ug5lYM)p3R}S1@+n263h*A5@4f%f;RCZ*VVFElc2~|^+M@W} zcA@At&?2O`J)E~#;iG}v7p*s(&ZY?)80w}ePTMy4rZy!Q_jm3fB~I45EScp)$KjFB zET+-mcIC4RM309(T4g}Ov?^>mAe=V+cEIGlU`y|~{%TJ2_`C?$sezPP zT{d}lfpNh{-H(;3@KEQ9Bz^Q4JL*%bCC~Zh`7XSb9Iw@HK;5hfu2}4^aMBBzm5aBw z+UJ)|_cL9jn`MuUb5J8a@WJ+7?Qfa30TK!)MgDa$YeZ};bmNDInyv`2<=;MW8-`KM zijZ8fzM$AOXLgA|<%slFQh?7YC0=|RH}E7UY74EKPuKhO{fhK=^={iyk!~9hk>fQo zC-79WhteV;uFM;r)E>Q}P679`-;D;gXTrnHusqoi^(MZ~B-ma&T3%QdJDVrVypXC} zvUtGTG&^2&Ww+itcls$OHg;{`8GK+{nmwO7AoJcUo4nDRmkE?PyuQ&jiL`<+K7bON z5>%&R4~F9Pu==-1ZxnS41H7-yW-kKE;4RsJHgsPVvyM+%iR^0-9capGb(o(UupMZp zXp}dU0?)~@i!k2Ve%RM?ou_Ok%MVx}8?_|}baS={c8HOWzI6rfIBx1r*`V4HL(jDq z`d_#yh&k?M*nX!@%IVpvew=s&V`XXI0+5fbMt^k|%sNFoF$b9g`2b9DQ+^kVhB@9W z`~9XX>^|f71z~X+FN(d1=>Y>LHd`Ly+q;%sA*$%ygZ=o;kWDL1V!@kc?ztmy;^89q z23Vm|LY@|=D>z#+Afq0D4nIv>Q-hN&Roq?DsVVwg0z{Xeyn}N)iu}OolfZ-P^2H^Z zKiJbZ!Ik1Zp?>!T>_cY=TnX>4t?73uX05BfJ)`D$>N2(=lVFrGcmw<9+HY3BzACo> z)}C2JN5)g3N%;}m2mxZj8qHcyjw3zS?s0LX^k!O~WEme@W>R2e#8&IkLzUQfrCSJ0 zgN=9;;;lf9D)_rSH52s)MdxYGE{pZhoC$E;$ZfHtvr6SjA+hj?X8p9%lM>giY>?9}um(_8sM-PhtpL7hM>+H( zVm2Lm#;^pkU7qSGhwwr~WG<_1UZvcYQ<()rxg?rO+93q+yNK4IDeTMRl5&XzF+k zfR#w94SB=`Zp24`!^nkxb<~Ctk?Mk0y)*QgY`7d1wo)bZ+4@l4mjn0N0qPt4xl1Po z3Ww}+ve$$#J^h0Ki%a2PMaeBv7Y{6qnq2>NZ6Rz{u@Y9)RaXM_#sjKt)kJSrCE9h z6|o*SmX}werOc_7Fhrv7kZliPG}^RwDJEgi!TS*4-MQ?=+xa0{s7C~eOU+0z9&;T_ zO?jD4v2FW!eS+zs!ZD2)PzOl1WCTaq2UJ1tP*xes36Bnpk+SN~6bLASNo%Im+G*pr zlH}9)X*p}U6zofMpH`g9y-vFO{W)zrfIN66q3S9k4a$&?D{qj{q;*HmU+}+6)Im(4 zPzk6x1);gq%(9Xb#}VO@$F@7SA}n4iYb5`x0s7@-9uQUmLJI+{oG2^1K7EeNo8TM+ z-Iznr34=ko?;0S{SxD%2agB-M!vtSL)g`+dPL?0a-ub4AiV%deh+m9*GNKOqps}vI ztRS`z9_~;KED%%Pvk<3&G*SV6StAgWxXaUVon?Ms#wu#x-zS}J>OMY%A8fTTlo7BA@jVIzamtDg8&FyT`P^MIYl^ZCVzKZ~h*f*Uk?_=H?G3#g;B(l&aJT%hVnW z>1~qoJuDfA;)^qej6E0wXUM%2i64}GR0MlJYd-=Otg7TU$p3&p{+V6JV0tm={8^uo zKg@~$KXblJ|6ZCZ|CTYaT5(!M6^6_!913R%eveU-`eeyrR51-k0^VPmIb;Z6MLls8T>(ZJ<0T4Rb6P+s6WPz7iYWjM!4S7Yzn1 z5+${zTwA(_EA)=r46YrcU$FCbSaA+9T;EFJRcjh1CP2JGF$-_R)&V2?!rh={8qXSx zLWMrTpv7fxi2uPej=UPw>b?vSh_n#Ea&=R8Qy4`tUfX9L{QImyR6~%bVKisr#~3KL zUXR0|Q-=zodqk2|q7ABQ^iujt^X^lwWP}{!p=1A-&(@Iu{>h_J^>>tuomEEO)rS4O z%$eV4+}5uEs}}{b_wX)+yP6+QiN+Lq(cK4>GOrlZfDo6Z z7HzeUUgRx#&VUih{uhcIdg2b_W+fDp5xXC$%2oljr=SNVI2ta`bGu9@!$tl&9D>?b z#VO2OCsvBpKNkpNZl!$zkQ6r43>hSNM?X&_f+2sK7)}#>IM47jcRYlU#uTjVVzx>) z%!jM`qRS?*d9TIiwYbUMe2M6nWkDe{4g?qU#rbyI6%FV$Zns+iqTo^HcmCJOE99NC3tUxz zj`Ua1Ol?G6G~^XihOLGLbh3^^Mzgj2(Nl$P)p8(Gms~tMHIDLMW$qdM{Gxupar#7E zHq>k6gxAMC(K((xTJcejQDWmqqxhopKqD*iRu0XUb~9o{rqlwjVDvT}*=k7HVIg?X z>>bAbM@2l#GYH$C$udJ4)%)%|^9=mp@?t1o$}z$)ysi?_;Ehq5R&`5j$x$B`czp)W z;bX;NxYmW47V~gl+8AtYyhX+4uLLYTrG|2)QpqaaeSLIo_Wa7roUS7e5!Eqt6HVd* z^#ClJULXc_UV}Qa#RV<@lC1-mOy(`m#B+l|SG-Xdj9Hq=3Sk)M8{+3+`fEhsyC$v; z`e*jP&Dys0%wtPle}@@Thc}xi_Rj}(Hz`up=O0B}5=}(piOO?xa^`KTq?a`|*TYCl zNd>I%Y@dbPiBIoKzz4_;w6*J~{&9XSfU9SHgV`ah{3BlPK0qj8>hVHha5|ybPzaav zA(e-o3b-bLFb2Y#=T0{&j*?9O>qi_izRGyj0~2hOWNlW)a`8CS7AMD>X1?-@DDDfi z)|6dHXq~s=acG4h*GTj!_d9SZ?8Px-{VWDA=RRi0V410d3BmT_{OV8hC)>l1C)aQk zO`$GB(;kK)-to1>BH6(!V=zN_J#zQ}cQ|YxcT^x_fa4f7*k2AU6;%2PYC|P;VHx$l zz&d{tV-&$wjKL-#6b`c&WQsRPk`KDP3WHzfcX9k-9Y&!6fED_I9Sw2Ypho6JdSMNY z==q)5vsqA~pVE6R;k+pOWf@wz;8KID(N*eu=lA^}2P0WVjXXRZFkHXhIa}K(+8G=EJ2omLpz0o3 zss^v+g?%T91$3W9YU4 ztoFkEI94#nz#M9vDe>f90TElt9w_*}v{QM>2!~>+DC$3RYJh_mmaTABXBV zVCWCj-YPzQtPwhMnui*g%2`I8#kGAu^UNLp^!^c9n;P)CTZ^E0)*{xLsxA(A?=z;y z62Q-Eo8DKGldi-N)h4@$$hme@6*f2tYpzA)v^GuNIdzc~7DA|umd!6el}OP^(BGo{ zzP+oogKynSC1XqH;k6f1NoNm12@XogTnC=M4h_|({M0!uWNtCZ3#UQ4XBSHSeLA8x zmXVkzPhkv}Q0FVMDrcBd@%Imh^xK^6<(xfYa;>=S-PizSl?DEJ`L{ky|HN#R%Q|?U z^ohK?E-O>T`o2t5da^n{Zq-Uucj2XvHgfXGa%TSbH*(5DEP6r{1#vwxt?_MLxZ}V{ z&k5DjgQNNx! zg(^uMsaA%*0^=gD-)F^&yflv(dqfj|xNG}E47r%=aU5ETrp{ZW%;cu}fa~&Jkp4$v z{?Fw7AKE>D-M>_U{ugTg&p-bs$NwjV|M%1M?acqTry~5%r|KJ7o7?R{(8ydY!oCRAt6RLvy$Kk&)Z>JT;t?}2+lN3*O|Sp9ETZ$ z<6i#1$m9{-2a-LE#6e|G(WxGr+DtJ4^q^u1M4;m)xZwmF z-u;+z3`6b{^^lw zZRF(`Z^UGKm2#vYgQ_#oH;It}uZDg}uYelNakHXZOAnyJ0lW-+c6CFJMm^rjJa_|y2yek{Fefi()A&P)^FQEq=+HDY${Sl&pQ$D0`=cNMmqOtwGA{-W>==v zBB&?jiGH|xcSf17Fduc(_y@8uL{=j~nps|>i!>L=3|63BpI#mrtx;RFEuZA&YBq3k zzqi;roOFd0322tqAIAc@^Sv?$0p>4MXK#_gSojdmu9&6CIce$hG_=l>ggXOz@g7gj z?7MlFG9!qIqH6}}YrcuRK8W?&2%?g`Kym1FnALrGX_K~KigoJK=g{?S_al8fee4IU zP=Vj#WQGwkQVLjPp*FiVB|#R2HsAqNA4_&I1*^BV-MH@@t76rI@hV%18=E6KwVRnk;<%SmCKTEd@4{%8wW@Z^t+l-VmWCnmlQA8n~&pw)Y{rygOT zgKn3ph|ZE)r&>DWG&2>m?E<9^QC$0bwleU!GIw3X*bKlen}880NdxPLpb_GQSeH@m z`SK)C+GDeX!jff~C)h{YqEdK8LHesWV6h5DR8l!Dmr)~RE}wGgH}sODG$F;a#)wNc zc{2qjO>a+ko1HI6*6cfHmW4#fMukiU@jL&|#!9y+sP8)JA@Y|(DR2fv$pk&Sx$cMz7mBW9A|k`32yOm-H{}=YQqk`B=$d1E zV=ePllGWxEK~UMZ|0sP8NaCCll2BNA>?DT?3GQ(!6A01Sq!6a>z{0mJ6q{>9mA|@d zjRZ*C zHMj=Apd3VYH~eFmcLJbok6LxyEG!6vbrwu&wqRyg z3e@Q5aLn?_i=D)p#>8IA+cxGd6dC7C2>a7marQM`{OKf}H4Ns?yPO!NCan3suhdRQ zru5(vLa1+=XpFR)ZqSB8 zb7gRAbp5;P*BkZ;pd+pYvM3I1oWOe0=Tr6L9A6ieUUk2S5~8*iH#p_@BKTVfSiwIK zoqdT+&>JzkOMOUR0w}R$ZsG+8*w9KACR*~ZCXkVpEL{8|Fk?|Gq?)=mNGWEE_S8CJ zfWi7%akDvAd>GglL|s7`Y~N}A*UH*paAjkKAn+3|gv?gI;)fe|JGZx`k2t}RTo%JO zyN+1eJ;&P(AkgXO8g_R#_qWsS>xplN)l(-2e)U-wv!udEEJl!?rs>F+-vp^M(l?^V zw&^HmCN_(xjxv!gSkj~gMEyosDP#;K&Dhza$!402Cjn)OI)NSpV6Vv^jWk# zU{&Il;G}IJMGTCnmcmP2>nILF$7=9SB7{KPb3e4>>VyNoD6VC34&x}ozTK}QC15+L zF-13*)p0Nf&DUf3b@<_11%7T+)t&)g%F(?1cbOalGvjCes2Zu+pgqft65c@T@= zSx)+;W%0Rpbyfff->OQwp|D3vnQSGnVmsOohc`gLR<))9Sg~5INIXCVh8z;B5-+nW z^EoCnlHcFojee)C=*yB__<>E8FNwrgqP9cb^%k6ZuRSa787H@1EQIcK1%X$o&on13G!0D(DtOMXpJRja1AaB)-S_uGZGuLPE)Scc z@@0*^WwubYTC}Y(k?tB>+o(pPb})qM@v(kFv@9ejI*aBT*%YIZC4t{ub{rew1Q(wY z4hxQI_%!G#n1Cr3x2C*%07}Be^;DL17S@p79sJrHBsOq|g|+f`BMozNN=Z(Vc2JXP zDY%Q%)^QW#8ql)|Bof>1ECpC4s4zsp3Alxc%mGR>Ss2y_R>qXG)E}&)vMdBGMWUvB zT?|ovxv)4qq^bysWvFcrlTo;AzbtUUCH<=G?gLOfB#Y=#Bw7_YG8LLlf13M~Eb(W2 zJa=Xp=o`dmNdb^xu5&}v*~+2SMWb$9;RPu|;dea*RD<%0>ssVogEBnyA^+c|vXgU- zCXlCXPb_Qo+2uxP^n!Bru}Ig5K{J3;)2x6yb!cp&$)TV<%%pyej1wXQE`7OpRGnQ57+sGWOhSc1#I!3g2z0P1d@AQ>p-!Ma~} zg;K{&7iT(2ek3OH0SR^0p88U+-tn6jP!U~M{XA*f7`v!Cy--5!$?=Xe6DuE*z#3zF z=#%npcR;$^+bHfkQ&n?VG#ot#{{YWYamI7`f~yS42jccqu5^V204)C>K*pn68?4Sh#V zN3&X{TfH2+8tBBu2=6r_ss=G9Tc8^|PwGnmxAL8Us$Hw~g*E)=Ui@O>z7kuq4Se4x!y# z^IyG?v7R*aED_~kunkR=>bZdBPcqlOs)nr*WxlmR2sUTNHI(2|21a!Qo;$nZNiree zZjCGrwN&Z`3^=nGeXt{%khsV!?&p$Q;p+f11zXzHHV9C243t?Bn*0}y=#_s||1?o$ zO>y`W?@!~^e9@esi1Gh<@gvt|062F$iyPfeLyN5WiWH-2HEKol}fxQMaYbwr$(CZQHhOoU(1(wq13~wr#u5y`Ap#?c`7Pe%Y@( zS(#(5@r}{A^NRmJwWp{7I``DSv^!fH008R$AmWT{?2WCRY3&{Uy+&65SH5lI>M9?K zJ@EDk&6m_u+6zz|z0(L8(#$UugwnHq3eW%w3HY0IleVv1^qzcgc!1gUwl>_yQdW&sAc z_s46{kCoQVJ^0bxybr8NRWZJ~a5#FnE^5pGN5nKk-#{$wY7=>jF*h`f5A~;oF2$puRM1Vma_32NJq> zYFNfBiiWgNCB2{Qkt8O3-!ywT95~V;djNxY2{xaz2VVz}zNnHaY~NP!WS;`Uy@+Q$ z=D;+u*Gpo2YzQlVOUp#jrfJf5fPS@f}ewxpL4R?0(R0Qkm0oD6s_0*ezBUt5rR=>qa66f7kbiY(bnb74sVRrN6n zvI_FsM#A@OIjkQsx9<8^zTY!1#gjW^aU^&@&e)&uejHFeC)ZE0)0Il9Rp;BTC}SaT zCTVfN`iU4e3HNMd20DLMagKXpl3a3Gb;EXL^?~?;uS>&>S9h=F^ucC64KSJmricpy zo>u<^rymyDuF3Zx;+t((5=f`FIrfjC%&8W{fO-nzjb_du(2Utjwo=vQNEH%lCm#3- zj;abSolo!RA9};nY>~x7rHZP8zj5Q=bumd}uiehh`ei1oCKR(z_`sHn33h7Hhz+oB zf-&j574eVS50zI~4B&9Q<^yn~{p`9|Fm;pv=Jy2>quwvs3^ zc7-zb+bq*E3ZWdeHJ#)3r=wAcwHfpYfWuN2R4s z2<$c|l7$i?o9e+|ZzPxCOQ6(kn)S%G>!jNe43zqv2suOuw#-FO&r(BBhS|)+pU3S<&@fo+!ow^p&GY--i?{-ec)a`^4P8bp zHsx(eLRL{bkbMd3yYFR7g;|>(>bh}(Ayj7y+%3QnUIm@Ojy!Kh5=_xDHPJ3tx6De- zFceFmRICG1yTPF#ZD!Kq;m9-(8#F~+^vEXC>6#VV9LgEp3YvRD5^{;W)%9PBw19$r zHVVvxt^8_dd$2wGxutSO*M!>k>Kqc9>90hfOi;u9vL6?!zVm^0b-BLe^DoW?^i|fp z?dF>EZn>tl#m;E^x~ASWb*2I}Pj$IuSOoU(s+*&2$x-+;DMk?RLfE7Nzg4B+xprh> zP-oNu+dTW*(mXx{`l7C1kYe@N?a|mZa#S7IYUjh&^C`u5IlW~sbi0T{`TWs?+Y&ID z2H)~G?eC>4p3{r-V?gCMP`iE49%u@~&xIpQQ>>w}3tei>E0!1%wp}Wpxk+1B-JrH< z3bV$2<0)iKu?klY-6pZ@trUTu@q^8RM)j-?Gn`S$99UcJTKGz|iyLmz#=--Uyt=r9 zCu)rw`NO4xd#Mql{lZD==yl`D#RhoH%R0qJ5Ggp-%Cz9%1kc9{>k={DL^S6Pcm6{5 z#%=2zK10y+0zUHR%&n;3-i9Mm4F!~?n0TTH2*aj0{&|{_j|O+PpZsMTzXbB;Toi#) zE6CTIdn`3^0Ru(?Ng2_VWmuP>>)w3V9279O^U9I9-J7n%8zBc~{m<T5C${hRb)So~ zF!y>G>QQR(5CK&8EJlSI=1nwN+eXEHFHD9N$-$ciB+1t)CNr85c zIvU*Ejf`I_Vm?E^_0m3cOz97Zrs>pcr}ubjW;xu16xhFuGFCvUwJ;@D17#Dbm>u&( zi>z<8v!o-*b00j*$ifi3gCn#q;rvM78zf8NP`Hi)m2C*rfrk`>+l%+(jo#V{7OBEx zXjSdUfRwJdP!60+c>A`W1*aY{5Ptd2V4gS_h*w9Z@-% zi6i}59X{$rZ`nSH6#Gv>-MlT3JHGeQ9p9^#+j%@+eS%q=`fEv^xpg4TFY>=ip`>xm5rrvWmwk*Lcq z(;Qr}N#Jp^1eruJk=RQ2(a55R9V2=E%|EKqcemI~arJaE$}e?c zz)Ova3^NDM2G}PGsqh2+#S=)*!(^9JnhCUM=$gH=71<__o{|9fAOfnK(Lw0m-le%v zJN?meliHKGy}xq_nkCCvbJFk#y)VHg&HXAXzAl!f?lWy(V8utq0Ze=a^kKlFXc`i= zOACw)ove&Dy=R@k-9|mBw;ED-Np5R?#l`p4O;fr5h{JWWvSZSDJvjL3i3j(U$hVWE z7K)uKMcVf?7_dUAgKIZ~8YDb{yBLtBuBmLnVr#E(rvj&v+%c*cNCJ|EX>I_ArxMyx zn8VOc0^p}4rv0N>^K~ak9GBPYv-BZM-zkZ%R2W#=aI7ZCev$3gOT%bO)x-l9v>%>W zi&c>mA=tj=tI>MvJYU>&WJH zpUP>Ms*D+ z6=mx`zeEyIn+)*opcMsV^dWv)VRPg44wvppG}J6XKR_hU5~r8lY-^rRq0f-J))wHq z=*`9!J!{7PF34JCAZXX1YXpNlAzb#?mw1S!b(lFl4jD2tLpSHg};B^=ryWEUa-{9Ow$xkV4HpWSpEL9y?R%EW8;xwPBNC1PAwVJGM) z8(U>yqMKeLwVfGmO^NJ{c+08#ycm_@T1fh)D&HE`VfJWnQ)l9n?ceV<=YPZinV68% zfES44ZE8weV>%D}^AN9=%!FEz25$Sn7r!dcdrYeTQgHos%{ zvN&77)=sw+xkGP;#Czx<6y)$dDNwc4w6tz3R3_;?={*)W>{FYh(a`02tE@dd&I*?< z3w;rr%;buzh_%deK$qwXQEIY+#lQ|@ViY#bY|}C~-AH^5rfcN!DJ+Hr@`d@!{rrN6 zx$!1*+m4%coB>2{Do0A&KLq3XbpLF%8Z#b@8E)KqIm{0-*||`?x`gXsoQDBwe|Gf{ zeg;rNt)}Qb6c3K_SfBEQ-oSLMm6&}e);<`Yq-|OHM_8s(q#+$ZQEWLiI2Or*znKcH zWQ7b9zj#v_mx)_0H*^`}NB9QNB0cn41K`uLuemv7Y$GhiHNFK+*3UYTAlZJE~dOF5fy za1pw+qsf^l3}0yh&D-AY$DFm%wOeCF;7HbTUGhV)R#&vPhZ=MhY51%Iv zg4coji_sojjpxJ2TEx9i?zO{nzB-W>OSh5ekK&#-vs}il8+&Kf~+W!Gw1vJQSfC_Vt-wZY08(0-^iQf$+(Fd>s@GN66?qjm*2g4OBw#SNfCI1O^ ze&^q94^m)G1@Fo6I^D%S2*|z%Y`Tg@f5kA2*8Sa$wRW7(4L=#8#&E?wz`555Hti@$wWIO)T28c@w zJ2>LTPA$S~CgA}ImKcub&zU%mF~Q3@dU6$NTWOu`bl6I?3P+r;*3ypjEhlicm!)YB zXshM0+a(>WsWM|1T=4is#%*_-fZa4hl0(V&Sk8H3c-)qz@RLjJK;9+G(kqu{?atCg2@5QwtRSEb3CGIm37AZ5KGH=0)d(WHhpqt7AT;yr8Bbp}glJ zGx=cMXJ-O~EyJJ5=XZT+gikA0w`o>s@cP6wPUnY;!{7+#%UTB@XDJOK~qm;Zn6#8jTse^UQhfp{hH)lUF1+mCq_p>c!GS8J(?C1+K5_P*SgJgT_1V6opD;? zpA0~=?RZ+o>)sK$!Ev7Zx?SFA@l|;cdxoA?-a#QNy7kFE4Q>=zTki*+?!GSXpSe7l ztGF|?nWTPG5||C#r*EJXv=N$ufgQ5yvY$`+m&d)LZU5$QG^-92ZW!;HDyT{MBtxkT zk=#@#i@r9}l5pSZ6i%$K75s0i60n;ENHnR76lBfN$o;fBcJ)90FE1%GXcC$A1L2hAtBg6T1Xxqa=;g^vlsT#^ak^D8k@9UZHTU-bUI=>Cn@76Ht$ToBFkFeuT%T65O-I^(u!PF%~*~ z#6Uj6t`P*a(B&f*hUBglB8g1UIr@{k+zGVq1cfN=9mWcCXh9v0>Py| zCE@s81ZUo-OOWH(2SUOSOKmpU8Z*r-Xp^1fp&!;GW>AW|FoMk#=B=dZ$WdYYm-cEB z8M#{!gH$nWpv*#WNCR0e^PV=?ZHiGaU^ojgS@)pfPqTrLZu4Mt1+A+EhZ+w2kNF4O zDvy`k`)UhRmY%qhf}8`eaEz)MW8P)iFVaJ-LPZw}t>gPkFw71!Jt}YH3<3T0I!lrU zG(Ku6jR7BbmXac(r$OTqm(gVeU1xA$pkZ7j4-%LY*SlLI0!jPtl=rkVSN4cO*D_|Y zjULfWWMF?Fy}vxomijGNWLWq%Wa4VMRX*$5`!W{@ol|V0*&%d6fuMKw>1|}6$^XxTO*eaYxg8P!;F%Qw z0RKN6Z2#G)k5W-7z(> zUfz$FU@nqfXHG{a(#AJ6!XY(+u%OWxYdp6f!JrFx&E|y}AoaE$RNytNR>MJNui|VY z7bWg8I*Oj;M!rF#w=}3%TU=%MToyy-j4Kyln*@VmMc9Oi1Ul1UMIB?abB?B@DWoTs z^QRd^y(cc-N8>h1dEkLNQ{a?;a8DV7flBXr?9P7e7uZF2SkkHFDH?Aud0W3>nX4ek za)G34U*8N;Um0MNfcy*)EiEWWwz1{BIccp}H*dxgh~7!1c5Gq3Yf7(AKa)r=KIrO_Bjz3H3`==0#gs6D zXAq?vw|M<#DX+FsmP#z>L0gYWRIl>X$T3tG4fats6KJ#4>CF9-r)vW9)STrvfeaf= zuDz7g{Imi>m{-x5`H#%>R-jN2nePL=lZ%JO{#nv)ezX~j_`9{p+NjDO9vBO}#d%z) zB(v9ndeXvBhrgZ}TVn|tgKw>Jz(`+XC*mXxy)aosx6p#`!N>&X-^@qRkQ2I*&v|+P z@ze887x*(+1f{{y9m&}f_Q~z!aajCa6b*AqhzGf5RN^02{d1Izv$1Bps1Qt9ffO)? z{B8oc>$TQEfwuilNm{A}`w+P#pzyCqsQ+q!32raNdf3lM3?}%CrX@+AbdfSzu)?`nyPXtHL;!9T=veyUFtkrfI$?)sQ)SkbkLXxYu!yJNk-fIgi zU~tI%ERQrb_j;x`yDY&wRcKSPraB|gN9uiz)@Yj-W}3kWRIh8D0fsK!e&W-|N~2c^ z0fVBbN&cTP3~R!!M=+i~t(r zHQ~j4L^#iEKk^>dCyR@!*@G!lx4Je0S1-0B3Rqo2hj23P=P=&_!nPR~zgV_sEtTD2 z#K#Z??&&V=Y4J7ZJWtE0-Q{bRX`YDFHp9&&~1wE zXrGOPk3t5$KyZ*US=AQVu?+5wDip*8jm+eM0Q0ruap;bJMQ521!6bD(+y?4@!^M5^qhs5-_Iyr8 z=FtGkTUBfK2at>ntI7(JT_bsrfx5SgLB$?(I+$a}=e=7DH z?-8`tr0rK6W8Ka&_g&L$35hCpO!zzqo)GAh3jq7r5%U_Dx*HKt96exge36}8fQ02t ztMTH*YM+%$?ZW>`ZAA8-|^qS3qm*mLlzo*a0OKwh9C7P?WFtf74%qN*M8maddM z7pZKS5zWT!!66Bi?J5-9VU`lrC$1{AMK0iucAstyom5Au71Tmgsk+IW7KH?ysmzTr z7G4D}xPvY96Fs=2T{uYlFiU?`4cze|cIj^x@twMj`x`V+2KgM=+QJ`VKQypAp^mjB zPZ%dGK%gP3EXGoqN-gCf5ndtSavEw_OGjSX?*`GKlPTF=1P+ql4)ik4(@xo9JHepV z_B*S)RuEhymV7iaR2TjTdK#@Y;>!u8C%&SqJY++2$Zbc9Gvn!a6=c&yqpbMK$ojg zTx*fr7PwSpscCcL4ZckK1!g6E@d3bh<%DfJ4@f7{aWI-dr)*lpD|CoPiz$YHvp7ykdni_eB&y0kBAJr5XIANk)h(&_R?COQEY*54= zH>^Tf^1rccn><-(&RO{sxw!A&f8({*N9_DV#H#@0B4_#`@-S=u&mrmRRZCbp0bo%g z?dzseeK%;ID%Lt#D?GFQA?^ud2My zj!f&*=Vwpay4$!}`{&fondWeQB>!k3DyO=TvWt-$Cy5p*jUA?@Ct96Od>?k1?#yEw z!2Pwx?)!6UfAruKu}U4wtTt?v*{POBC$5KONz(4ra_AF}6j7FWO|S#0bWNJ`Co5Qf z5~5v}o{dUHYV!rrgcbI_ToG~nLhMJ>dhToUKP7^5*UW#R47jr2X%Q zI==m(#s_M&7^RC`!N9Chq7;M1j?SUl<;I#5Ty^5F(`tZYXESP&9Qw>#|FYfLr&c=$ zRc6EZ*+JG8@U`pMo7Gp7^#o31DmLIekp+zNy>ya%XP*-Rcy&^sLLTMaOk^qliKjc% zPydoy;J?B-N$na73=XYah`tH!CS4Bwz*G%gf z2m+-N>XMH-Fh?FM|4}clU$O*3|DFAbLI15gjH7{CX74`gYVs5K$!BO-vwGopi@ct> zD&1y(-2ML&{iUYaz|i0T0Pg>BsW|^>==(1y;Gc+{(XsuniSOq|f1okr4_21TLf54W z&J^aRBUYK^QPa_j@W#0S3CVl`1RmtZO7qWc8}uD0iC|=8irjLB28mmqE)81L2+##? zM9jo-9KL<+&)f0Cfx5BD^y1txA2h7B#aD-UKY1xn%+M7eKOphW%yT$3Qj)4uu|u&V zgUZ>RG7k^l73!|0hipU}-vY!G|18bp=CFQ-S(^+H?ZGLKD&(@bdN>r z{#f)4@1w-XD7x@zEXc}Teu^m$ONR}b%>m3rq}uy%mK~XxQg3(Udd}^9`y_4Vl?8S{ z*a@Z>{59xtN$U3P#3s@9tuYT04F*FN`l;Dw|L_m){u6m;EtDiO)~4c@=VCkB6)dZB z2%eSLIm}n1X!yDquG0+~s+q5fzyf$-1H{0*IgP)i zETW*Yctg8=zA*EC2^ty$A+tF5%T{pea*@h_kgm!_6Zg}EZASwOaWFJH;-mRP_ImKq z-!kkynr~c%0FJD=GT>fNaSl!-$t!JL2I^Q36(B^4Ab8{^_(ZeRL%N9^1wP3S9p6Lw zmb0Z1_lNxsiX--l(097R-J{{az~J6*m8b@AfhvF=8l&K1gOKt`z`>)8QHXK~5#*qC zV>HlfgS(d9Dy8YeJ65rhsWVB@UVOVO@<)@7s(G`B1lG2$!80T4tCQf zAor?VRhM+?H+Y@U4~sDqGR}x;PHwGX(CsVZ`aZi!zkEhdYr^7P?Y(&V@;`rP0D*M& zXry-jSj_Wbq9yl**DIXUJTV8_v5|GTp}l8Ea#&}6oIxdFX6c`42XaF7*U|4wqXmy# z$zS!$x5w<2l0nRB+Gq%IF%-DLU0i{%=-ibUV$v#MPTxBVj|un4~$4X_l_qWU!s((MgvOBh*Ath2P@z+jO+j@cN= zE25~aseAoID)Si*UZ4cVgVbOWdaMIi0vz;N1to4_)7?-Cly2k-9M2rwn0fQ=*5J`s z?k4bpldSyxt7@gsH2b$^wciT!>oCN-6Mld8z2j%n?{<3?n`tB)x90Pq1-#F_@nA#Y z;^qF8o9m~02G#c8nZH`I01<3)W=?MWwPZGFQa?)WdW|`= z)*L2{*RwN>;dGfUg{O=0$`I+l-NF%y$iWsN@%bc1A@eHQw{xP(z$I@o!u$o0Ofxi` zM)4#9Q^;t8bEWpC<(*ax5!1!={B87P_RN!!FJq^j@Ya5T+YE_|M-@~ny;5z|>KCE8 zd2ZH8Gjumy{n?sKQseIqjEVcpin^R5L`k}xlFi!H(xHK^q1$vZclakl;+PK7PF?i$ z(Gl6pqDd~>Md}hev%P`dEb%^OJkWI;6%;39(~DGi-zuFZIQZP~a%a&j6qe~iPMcM1 z(_qj}%*M=agb(ru$6O0)>Ua~&Vr|!cGjw1SAuyutVQAHzuLmDhkj*o;YheCYM7EZ* ztUt*v2Ki7_+eY|FlYlF$ORjXaY%_HRNx%%p;w+uK@59G3!~U<0^BSslYvNxi*-)|0XPCgne zB8;N$mqh`i#g%+#m4DoP(xdEY5Okqs@ri};#4p9@P6v%M{Cq5B8Gkn(djqiriW5h; z|7A;3l-HtB`%$crXxFDdXR8AmN8xE`^j-!TRsCuAO`fD^pXiFrAYG zx8YpUCR)Ydp5``=7N#Logue@F6iz z=69y%4tpn-%o z**5$vECH$m#`ttoXA&tS(1n=q7z`;z>%QoDZpqE>MjNM~3YZlKk#eY&qR889>(+X? zRcER*%$ZLlm<#sOx{+98k&67nxXNpTzQ$X94{3hC5wTzraSUuVfii>wKgtaN%G+oS ze!K!03hPCoI^peJmMX)GoMWbBgujhd@dWowo$pGO8suF;`T60#!GOFj-X*nd>pnRl zlq1JZS)X>>P-oD>ymH`Ho!1j!d&-rgr+c9iG{%QTnrw`)(NjT&SufY7V}TEas{h2AzBLJd@6a>R+|Ca+%e77Rlut?W^ZR zqR(sIZOxYokMhmkTzcuJ+`~vJ9DOBXYo&mO3IVd$;nOheI^w_eXT!i39zQ{3TUatXIUD)Ud-+SE z2>=G|`iydi>DA0Ue~UH;j1~G>>U0w7~-G&R31Ns-Fe&hP|nEF}3J`~Kv5;4uG zd&hZuA5+vyAan<#71;Adniin!asf)dnW|CZ;vCm&#E27hD-?a0snMcl+mr4C< zEeN5hq3;pW`Ia#>&HW^Nj_&)Z^n~`iSneK+EV4XVU43XS_F`AQwT$FO^F_U|lzVH! zcOiM|`KkyzFk^~M786YmYQC}34OV7qdC&Hnt{hArQE+cuRDTnzNXY_=)9x(j=Godr zcE5=s;v{X-{~FGF51d`KTKD#O`8n{>+Oj9irRmwL zvp`CLd$}i-?Qx40Ti8u{7jw5fK8-qmKK6u6vvqSteQ#>0+{8TODx4T`@p``(553C{ zb@oq~lv2yldDje5{vHs>L2BzH-6pkhK;>q~ZD<{~q|M7Pf{t3Y?z-@7M=LzpOH6BT zJX+<+MRV=`j333eP{HN-m2(Emy1vnVqh98~6>Lc@EK;a6>panFkq}ox!*{8X>JhPB z5l_$5bR8|vQWalhxfDBWS?eiMWz4SaFyp|LbamJe*IO-2SGBbR8#~C}QChX|zLsGC+<09-B zvLgk1C$YQ!B7^7hW4`aCWYGY|nG{@(VuPlk+Tt}ed!jYEU;Kx36x^c?w|-EHgZQe_ zXmmDrceeB8=*9BiNX&jUmz2dOb_@44!5=>6xtabeRL?JS`uCyZqws}gs)2S3J|gep z;eaE*j3?i>vb}L|SJ~|Eoyvgdb#K{L^iPG6sbz1O42fT5fA!stGEp^-Es$e`hneTJ ztUAk)@R}>4eGMnm*|?M9=7$wQU5b^PPK&C<_Sh=2KI`iwUBt1hRdG4%a*$4sD(q5A zD-m!VG<|_}`O5$za6xgGxEB05))MpwWuYfpx>-Ya7 z1|IOz?mYfs>e2tea@zl(C#@V^P5*U4SUKC<(VCiA{#V29{}z}z8o$mP?MT1x-{^zB z=vPKC&1tqh#vlMyvK69$D+*`1U_5^!d5^{w01TayC>;xJroh6e%s_q?m*XarPL` zt#-jmIS&j-)K%!9@sJ6BVsfU85{&Z&>x_koF%__y9IYQiKQhu_XPTyeXP$yq%f|n$ zTv*7CV&wMNIdb)Q%$wSGh&Ep=-H|C{uhZwe2uw07QZFjvQnQFp=3QX_u;j>;!}evP z)nr0IDBI(_CQjR{4op zETlBHgJYy6W$M9RxR*|2{4p$*9o4L|l1kCy*q7it-44(#=YYgsveXe=`On}mV@6MB zU%ImftS-=*3Bg4Sv4o$Ye{g@xv7YKi{Y*fMTWWeP} zBV>=UO)J&xQ)e3&a%Asx(mas0k+b{kFs=BeQYklhIGS-BwEZ_z2V)t4$#uB zYCVauW7t#JjjKNpfn?1ixkV8_3A&|U^@D6jFocHY9_kla(TG8&j83FWK*OL*S&cA$ z22pw?*Allh8J5 zBQ8C6i;-T^x$av!1@8je+Xj^BIdJWOcR@_OA~7vLbM4(~<2lei?u(e=e6a4KT0gxi zSWf);vsiLmX%T1A|z2Y*$wl+Ch#cQmyR+S4cOLf&rBMv#O{tD-=ptVb zzlrTFFYj_@NNUwz1z{GfDUg5j<=~qEN44qtv2CD*|8oiY>&C_-8oMwOniw604%xtioCV)a_E?lJ-8lYGODJ-$@M zJ1O(rN`QR3-8)n^B@fZ!9y3;Q6}KKTEbS6>+>POE98*-b&eHy)gl$vgkIX*%=cYnDhhz#};`E=ZUcfN?#$n_V3UH>2?>go|VJ^AtOU z;e^9rw;3(Em|psJw~L!0zP5wT67IzM_)z0S*9p|tF1T(ogf={$judu ztXVQgLqok#&y@;@3e|0KC%!2hfHa*OLg)4b6=gvPK1ED_)VyZCR`ibkL?xr1=d2Pv zgrRwnABLgxIf;|RL>s4L;VZOfNY(FD7zO4yL@beq-IvC2(ymF1sN{?I=wmouIR#o! zew3nbHg=BsU3J}<`YuNNQCdVGHQ=ef!!iixndjA)7hA;}*r-?2yFxnybz8uh3iW5J zvW-et+i5CUg}xBJiLNAfUPH4dZ#<7}Ib!^~xCNfD96r$yCI+cjC|pQShjrh*x9%=l zm0$Tq-237maMtB+e;4g=vKSC_Oxk z)Xw}V;X#e@K~`<(``)zBmT8ipjC67F#L|Oi+tFMD6j5&Fd4&HnHukPaOIjK2s!IvE z0o{#@JS-Dj5cNO*u+hS=p7U+myMuT@(1Np8BozX&!}N*A^*C3EAapaOgg8|R5> z*idTs&?=VYNq^;_1ZVGsNS}emt&IvK|5o0;{{Qp=lu)`ii%OfBWNzDnfJg6OlR@gGG#b@2VIRdW z#D=pLFIxYmXJZpT+*6N#Q(H-oYE~Rd5zmZ;Rv|Z()!++r_htq&YYe1}1zli&oul+$ z;ph7*>ckDm)#*hzz(>Xq50114ZohBE&lX81IA5#>VR?zfM|Kkl0V1siTe+8r1lIhz zO~hid$kz$8B)9I@L1Oo@xT68-xqEW?h#=|0{8ldE6N`4^tcl7*k)|zV zGI?j5v=m0M$6XQW&RSOV?*id6N3Ko)Xf03@iX|f;v&haX-{j9eSMNUivd8aO%P&hN?(_`d37?5d?0bWgoi0 z;Q%7UOtR4BOFL%M-7%*g#G|>_z4qBN$nx1M&ZvQ^f7Unx=261conUH{&I5L8&XJi9 zs-`l>rjbD>&Vwl;&Maa$y9k}1@v8XxN~Jv-q>RFd%X&6BDpqx*nUU;W1dq%#93S~l zcz0826q(=IE;IA9R4o^-)M_-5LukTedq@qRju8fzQ8^9C%4q`Ou|xv=0`sv6@2JlC zP}rHv1(q?sgWN8z+_}3(?X2vrtY`%Xgmlc0>2M|(OcbUbDCa!Z)#}YSh;(dFAr%C_ zoZRbn<`Y_55oeM7E(4juhv#$i1oE~m7Kd!^B&HswV4Dd%=m&zr2PU%{{F|`I4j2Ip@78ypx1amVw5a=e;426x(J$-MjQK z^h+A^tF8Pv(B#b8!wu{_?jrW2h6Q~hGP1`>eBnD(=q~V-brV{I5zn0ymwY0|sI2D` zNYibCQs&51^Q@K?A(>$`Wk)0Yr8t4f2r8<=YaofX^ccYv!%83qc)j|ZKXM1vP5c@g zVyx{>jwW@6HP_oSBJIJvw{oJGCP&#b^Fu{HK=lWSIj%YMS96q<3#vo>MZd$F6fcDH zISiHLspNTx#1~5ER@zM+hwDvA1W8AufY3bF@qXX!vd5kiLRAlD7N4k5-B zg_Pus0b0Sn_r;(YO+f$yuG(AfBb{!#cok@0oF$==%6|wCqVR_S9^2N2g!<9}JURPv z!GK6KrBMc%xJ8kel7rg$Yhfc$a|f=m@!&mUD^4k5zA|(Dk6SYfcIx5#?Okv*mOV^Wwya zFWC@0W~ffy*grTvf-u5s?$;E0$mVv&JW$HP>D&LfE>Tk&nw_EncED32>0JQHW`M0O zP=YV3D%>lTRt1BGys^zx#Dmf8TSUpesHZT(o@FTkQ}~))ni8AR;1` zu2sGeGKRCsZk?OVJzM}{+@%6%-w>e2grMgU9MR#E1W7yy8I4k5Kvr8)^+M38Zm&$f zNFOpy!30cWglbxL;<9R#qI80b9brqKd7wEY6cHj)WdwMHG0CksEf=~y%K{5vTg>IL zl#pV*aXGm&mqoOkRJ0yeAq_PR7c?A{VZdKadcFG&r~;)hsh}woB`F+19M&ZUu3#M3 z>L2?V)=B7^Kbrj)i<+361i%l>Vs8>#E4?8 ztSAP|`C8XbyOm;)_wQ$C8{+-5nu2KxWqA+j!;w44U>#NkhdrBYm$_L30MlW9m$Jm>k|L%j``@o ze(iMX5ar}1W84S3RGdgx>Fui>kHok@C#&#zvX7IdD$~)j^BJJFE#73&M0Dnwjt1vS z@TO4fQ-iFHSf8qC6AuuZQBj4ywR>v%pz8C-2C?7C>-l~J?d0)728r1cnXuD#D@;hD zaCWVQ5gE19xzLxE3!#BaRlKX@M1gbWdu!h<&6v-Ic4~KUAqMfA6O{=$mRLD?DCz_p zySH#c8RL;d1;rXst=MiUQ7A|0v% z{flm5?z5nKFS>Enlk38Ja3^jk|V8bX<@ za(|LxP~Ug~|m=XuVv&w1Yyqr6XNm$``Tfh1}s-Wv1z6usC}VT3-EZ5?&l6>O;t zpH3@2J79lB-tt{O8=>*}LOwKFdaR!DzC+-P#_-cmvt-V`>1&Alq{^4Xb4h7mP$*Sm zlkkNimj9u zrd(~!Mt+BaD^ZL3I*FrEU%A7Zj_y8qw9U{*XSYu7Jx{bS!qz|L9@oj5Zq_>bqNW{} z%^C@)X&DV3us1Hj22!Y_;E#luwH`PUTPQG8i*Z)=oFbAKq9;%#P?z>!>Dn1U$rRih zm_CY@$jW%iY?0GF%ij1Z-pWW^LR9jbmz%fG=c3Nhm#LQIWxCg%FSv@Qpq8pNwPjtU zN{)|m9(~!SNtU43WEXqxyrjp;dpo7|3F5@7WvQ9A>+i*PW@UWr;zCEdV^;A72`j~H z2dVWwvck6$r4^5+KUQd$`)E`IT7IT6O{Da3LRlP;s|ceb9lFu4DMrYxF=+Ir_)Hkt ztoTrweAL}#o22aeVzvQIgOVZiqsiuBvM`ocU#Rm>@}VbP8dZceO+uxkxZ?sE;d;~ z2ggPAaJ@2ha{R>cuEBowRRPrrMD6h>7wNvC8pu5>ELU_dyQf{IPWRkLx2aq{$ zRm-wo)n)eXio60=Upf=&V5}v~cBjv3gcFjn+iItg`;JD8yO`vfU-0E^Wwnr>z93WJ%MgrFd0Y(fNii?i;s}jc1UBJF!j7=n-|7MQ=rCDd%{2VMWsY z!O)Zw+PQ|WQqdkft-@q(-RVKA1?L8vUmwcW;yac3)wFo$@HMqYB_Slvin7tQiP08=zdARwzybz^$f>Y zl?&yZhQ9|JwYKxMeP=yB`_T50K5UFPea&aeo80PK$mFP~&8+28lVwUvtE|VqKhkMg z_%fP;pWT`_e{}l6%jZP(N$rC({?&-bIWZ+4{L4Ha$PyyqUn^IxUI-W@B3bC6-FN@a z>(FE!DFSZ}Jnip$22uA#0!J37>&KPyicQ0uC{Uvb20<_AjpIIs-F_7o2={anq!f@l zC9m{_eTmrN3ym|EtIyP}S|%k!&t3{G@5tq(t zD-#H%@+G>X`lfXL71uk?SYo5qcE9ycXJUZoPegn6VS^HMr@4u7$gG>S!!9+8c@`udE(;OTA2nFZ4iiV|B~o%(vB zIczw(7Lme}vr#XRYT|v8x$!Sw({~4P9P>+!;SeXE{9;jrj*`3ye?-5nPc8M?CAY&z z_}{%SftB4DaHwhxV|n5#HshdV&u*_hZ1UVg;#QmFMSZ+MlZ%?rqP?A@MurS@^_lDPPaFnz$}oge67Lys5P=qWGbsUXgacD=WM#PrZR8Br!I^ z^E-#geNtuCqEU|%1SC4~z119T5ef@a@4vsaS853Pa)9mTLs+U*@&H9*(x*(izU&VRc6n_7at2QEj2Ie;Bot;u1;}9g$JK{GVHTddMda~ z$dsbyrsAl1On&|Myz_WkcN;}^L^bZTvE-qf3eT^+yi1=^Q`F_vL%px}5ZYtB`#`<#okllG+}vi=G#D zzSmhzpV;TTJ!eJb#S=%fj4-zFy;ZODYk6)r!BWliNAiy0ALl&a?xR<+_oIvXs7$$n zneq8fZHt7VF9;(2!xi3aQsi0sD?0J#k9VD@o?vVrq?2q&=KrLe<@u6PA@%Fqhx&Y3 z+B@eh6=M@ujN3;R>;}C8QKzEPW@e(2>0Newx_5#9Y5hwxlD^5&pp;i#rd*23rzf77 zUmWi-?}>3LB_uzf&Xz$rQweuIT!&0)GAp)AZ>}CIHM6=SbCsuutMOhWTKyXFf^tGo z9_P+lbUb0tt6IbGC;a_=p23ugprtB%24AR7&&kbDxh=9nOqgWgjh-0MCxn zk?p&yW=w3{us1TEc`kD-j(6V_z8*fFfBG%!l!O`0Sk+cD#GJbhLLcsj$9H~o>Zz&= zGAL%Ijn_rwz&ZNSXpI@;=SOBq4BKs5EsP#_H42?N`<#Z&XY|Db0@)Tk7S^+DzUZ)7 z?bZpRc{zV62DzwOpXeeLCKtMdrizmBMfqBp;;<|MmPqoBOG)nR7GGU+Iu`Xuj5ftsM}=EJFn zlv5R^PJIwhIdjEBlgWA6Us5?$|k|j!gS7X)|z=Vm2;!g6wlh2x8k}Pk+mhgJ^$W z(&79;^Q)%jX)E7j{cb4w6^1_&nO(e-z~xJ??2gysmpfw^uG3v(t94ONhwYyFb-p7? z*OiZ$$xP8R^oQ7Jh>sTYJ z^L}L>yn3#z_7nyO1OI`y0mySI)*ofh(Ki%Qsz3PNx~==Xpe2`)Rat1PuK4TAm&T1h zl#qG$?v(7)Cd#w#<-2^E-t&YlGn2B?OT7EBpC&H8X72YR2*hJ}7w)W3;P;e>tmmzE zTylIN(^%S_z_zh_pV>8g_)flmn-a!){<|)t<=13_o%drnLOMw33>E_S&1%tzdPayQ z@_+BHjE^a-5)qL&;CPl#cG+{X$YgR`E+;~qa-Vnjq~Qe5Yo4f&jp`RG{aSNr&D#^1 z{En_n#PZLn!&y7;6lgqK`bvUVAf?+lWW(D+Z0c@??h15})O{RSZ_g~yzF2ATs5)6* zbo%Zft4w9xu@#Oa8TibEVCkjiMdtjhsL>A%en*?8M(iTOLh{mf4U5>N$FUY3ED4O@ z=v*m$aGVhz`sGs;rw;(i|C2^Y^4l@<H*0Q6`yt#wY;zmA2cz9y(xSYMP2Yl zaCN}y1p{hl+MapGaot69-5HMuut#S}!Y{rWu(!2Hy}#Hvb)%)UN55bio@3@-88#>& z`kr;B-SBg~&#_D+RCw2!Rgd}j{DtYj6{Tw z4mrHy{CMS9Zjj8o-z5`9m!+SL9`(*#~t!6%zeEi zdDBjW<~!o>NtGogqbGE)sV{yn^k#2%MVT3&>(hBfP3T-n8yLG3v@O}elR9+xd&5Kb zyKL_rKA&$Tt6x0U6GU)bsbQe9!b|fLKBp>jcl)GfruIUk_3Y@o6JJ${@n0cDS!n{N zkIx8{lSUkI{gx7Y*HlQgdcKh`w)EI|w3^Fd`*MHgxcsB=3*sufabew!)j_9;T<`miIR5%5?Dh%iAvd>3V-Je-q z89S%WNKJ8@0~YWp_`T=X2u0c#pSVtN3b0=k6>M?iYP$YRnD3M5ilE_6hmZYTFIw&T zZS~X-*R|t$=VXcsU6ARe?J-t2%xA0^(_Xy5-8`smg-63dUg!f$cFJI#KB{xb?gP>> z|B;2jqDqeN@hddrIRnMRdj{Hx#P=MnJ73L7s`r9NAdcOYxo|Qzxcd2I36{#&c>MYC-UtDgw6wzFLB=OCW zy*1(nDWk~uVn*p-Cg(jOx(J`WM-_Az7oiqJj}k7U$78HsOAN|NcFYoo<$Aum^0?V( zHc`RQ^nCGY7das#mimCB+WbAdU$0VYo@=RVoIir*`A9e*JNCVi^x0u@&QhVI?27Gz z?!Nn***{h!_B?DgD`Y)g7;9$hQoP55R!G!v``3<^?$6xvdZwr2Y))zjr=o>hzy1KITuU9zZgDRi6$@<2&@P`Is3tl z165bYGjTNE@bC&9VTq>6l!oDjW4mI*E*vX9>-+lNESc@w{9rGWw(N?Wmd3V(GekGf z>6st;!meAk|4eF0Z!OJG)IWF$P`Lq_qoENVIPxMU6tn}(*vqE;bu=ESqe$G};aer%jM@&BJlA8R&xZ68 zEsDIBeL@w*t>9vrLUwb%=Q)&^)E6yOnCNZq-TYt6v(CSlntWEjW99(s=t-2gvuLP$ zDT%n1g&3B)jf;&gkL7hqgjCPdo_&hwOYZLS|aq=sA_V_~B)AnthpN4upBl6Xv^K#U5rO zy*d!8e#z(H(}`Z1pje`}#{&270)0)D4E!!Nq|6iUgEtF5tD3w^A8_6w#bD$tWKQ+W zYi<>D6%s5w{yNWs7<{)xq1;^E&=$^{PN!gyu)4*B2TrX5XS9HKGW_vB-B8vj4mY@~ z+uB~EyqG%Vd2SM^7qisyT>VGvI_$+GK22BaiRQ`>g_aej^Bf+(lRY%JucNLal}(z; ze9Bhz>JSmz!v)1QyXJN{ob|X1Py7txE_JKVqKQ({LbY{OHUP!ga|ICtQsZTl;5xxs+=bZJ@Sgi(AgyP#{2grE@>j| zgmKfM-e?V)1<|S$!!&Ea~RbJ0*mZGJJM6lWosKRl#H<#22Nn~3IZpMA2v#ZdX2bkVt@mPZ&f zZ9^AiqQp;1saCM#c?QC^0}-Egb5Et6h(JPbV^=RXq0^9PoVlvLXRc{Zzd)+li|)P? zlzXPx?V8@~z0>Z)%AulN)ta~aOqOT-(8c4kvfG|6>j|Fa`V<;mUm5ugDl z^6beB*|*Z?kLxehdE6zwoO8V>m2DqO5tZRQcXt+BdW+H(IR?5I0&>2zfJIlCj<)pB zi7wsX3FoPYVzm>xyD}*F=-+NDvfi0`dM{0_c3g-wImhZa{=IE=S-oig<8eb_ZjR3Q zoc_vM-wb!eE7EbA&&V9}@353=6)-w*u`D%KGFf?dPWcX)m`#QU&gFBRv3H4k! z8fNN#P&rTA16k^FDyQb)*9Z&m3D&1otK9~XVog%?jb&9;+gcX4Y4Pp-FRZ#^V5uB_ECD7$V#E53nQmwI94(*6^CF{_9;^8fEUdsD zXx84H*Or{7P*||QedpBMkq2iDQ-{B(eA@2t(63C%*ROlFV$xi9Y0%TKb(n4{;zdZN zf%}tgy6Obq_Z6-IA+g=0rfzb_@K+8;3V(a)nnaRZoYT-38v6dUSjQ#j^mj(a*G8zb zZK;m`uN?MxEyb>3QX2&0vT~n@#3RWhXs=aLzJ0 zFVTaIPsiOSKED=;xa&S>I)0W};DKfgy`?jO-x9?NXBflfMABz{7N+nsUT43OV|qqQ z>KkQXcMQyOQ^YnS_#W)}IuJegKwyHArIyOryM6pKfk`Z8cI*Hxe@cD`;D@0-knN7>EK zlKZ-1j}Qp%^uG<0t_>vVKQYF=TpmJl;6LOWysDbJ3uKsX8fD@4Vd(gT?QI!5BA%2k3p^)^HmH z@^^&zV5o|EEkGgBR}rrO*Oe5fPfj10s2B9q^6<)mKp`jKfOIwUw@a-xizLl9& z+RKzB0}X}41L(q%k=c|-g1o+_3Tz$gnQjAB2SOPA=bOVlqUK?w#8oQh zna}-%)wgj6@>f}9&&G47!EY8FwjD?E4fK$aDepf+Q7iPme|NlN-m$t5r6`@<*1A3M z5!PBSU=^0OQ$hTJvW||8PPaLA{Ze@qA0asHEeMI&TGDvp%r7?I3pp39KFC}blz2&~ z={iMoWSmF!^p)LI%PLMu#pY&V0aV^n5s&@7hK{NY1Dz zq`|2)&#BWxW^9Rzj?bwnXaZ}6ZV62mKMnfkW=tBS-Vs6SeW#*_xh3?VZm>|2gcEls zy7^17K~H7w5GCc^f?5%yDoe5_hBI_GOEM_f%&)zpW7dqdzOLz8kQ}R*!`?CE$K@_Z z2#dV0a-T>I@mXW4yZ-ULbT>1~bOo4f)imK5Zt@)nDSkgs+mE6=U11zaG}-2RgIfz= z?fnMCPn+kBg57&vtV&hJ*hZ#|-{Fft_ThLee8}MAfWZ^Lq}M1KKejz@xyw`=1s$eQdhwz2CdplI$DLNQc!&k+mElz#IiT&)sxEu~?Y zEUwzlCufxZZlwDvp%@K;JI_t}Bx5S+0+}3}(1XKfJNC{M?^TkeOnOv%`+D#dWR<*+ zrB}?&{-=Cj@9JgeHu~OEdzTWH61Mm4j=Y<+5tCn4*#nK0DIEqM<3$pZ& z_w|L>J`B-~;It#wsW!~ytb2N9@yh+XxA!{j{9YBFJ|kek!BI^jozAgf&R0&Ydd>z`;vprk76T52fh|1o;Hc0Lo|k^7j(9N+npSYx)!V3FIyI} zqx8Yj0*!$3W7*UuW#37`!NnH8Mbhz0H&!kjpuD^qDG+Fj^sMB*Vg7!gtx0kEW8k+E z@zFd{CLP7vPY*Lsv~qu4GIo%tZl-0QaAJ9@N1w#1?#pT7tGF#USO4iGTq>@Ss;C41 z1Y-i32T!qxh;og@s{4K0)EV|$H{bSB)Qbf~?VWfK(#rW}nxWr`VBkP`O`l=E?wR>7 zJx8@f@XT_08CQ26d|PW>-&r!`bethro8cgTxJnD7?X(7?&_03Unk>Z2F3LQD)9bS^yJX7b!C8tocICpEY7 zOqHVX_yTt{+ZWwkVrQ+Vg%zWs@Wzokg;8d=Zl?%TwvLq+>_^=>uG}<}erp~Xoq8hQ zsl)R?9nWmena29w>(bX$>9|%89cLutZjVf4v!SAjYOT~86+M~fOLe5=IUP;i5#J^6 zCrm8YZhYg6cNwL*SIc+RErg>^UW8x~wy*4(Y*TR^(;nYbA=S~Ilm{}V`&qtDZhNqON9 zCvnrO-Hf@)zw}n|;LESvqYXsla%szaN(5p08GG>ED(_0})k(TMq%a+wDHaR}`YWF+BE|7U8MD1o1`KOVzxF}%D(jMSD3T=ylx$zEY)9?BP-H{BwLU*9R(WcIh7FKXH z96F)V6@$b^ZlPC6tX~ibC{@Br0-|Yc`BzGZe$auzAWFMHo&PsVAnU-O2A$t}mPbW- z2~Z&g5>fd@4F)R?P{o0orn3xmz=@Te1p=w#4iY<}F5CibWw$je)9MOUc0gN`0GtK}lr(60{ToUPjqtE=gKJva!>!zu zk*;nQNGrIjjJL8iv#f=c-H(63&gPW*Yh)KdaTbBWI5DuP>2Seb2i;5?ay!dI>6H0^ zCNba*kpFcR2MF^1o3<7lZG%EPLA-#omZ1}5w^BMZ$Gq@O@K_=Xn0WA}n_pLPfH<(E zf6Gr3yvB8{*Tt;q{Oh|6+yW_WZQ<;;l?Rx6Nv=u*5UII=y8pt23|1a!{=ZSz^m|>%|w)!pntP;&xx9`s1(&#O3~@hr|D z*h!n&=a`BDm4>>E@;~W<&*A`|i#U^Bw;(l?m4)MP*DUMig&ndtI>naW)8^s0h4`apVg&=PyoN=Eb z1E@PS5T4{O07%^(#{UhmS#Zz^^T6wE<_DT3&*adg7yx!2D7!2M7;5U@U>MO!4(aU(vQ?~cPEf6zK0X$6RE`P!W z>(9wr`!4V0jE1|qg4GP{(sX>r$aWfNqdN$B(iqIeFW>^W!P7F!qtPhz*73gi>ZSe1 zz~oT@O;N^x)5pid)v>QT#x-O)OKB?*@LiRWAXwReBz`lOkPTG%l_xO+k?!8P(FR5c zaDn|(tS2@@Kx(N&aSvGoX3iGC=wonZLWB#%MnV4m`M2|z_p*XJLw(21Lx8xn6p&ol z12|j)S(-4$5H5i{AN8u zT{qK(hQ0XId!Q8zyBXAum}zFaH2&-J?Xh5FxNz!>p@Qk?&|J{#quDu5H6iDCxA8% z&@c-#DYO5IriQZp8&qazJ@O2I8URqz7>p5}`mazwYM8&lHPtqTfysxNZ-N^-{a@jJ zBz%8^OT12!-3Q?Ofs)8!a2I9yU*Uc`YDiDqw@?%}RWqbG2zp;AHkM4y*8fJtP)Y1I znli0E9tx`2ZlLg(X`ZpezX3FzF?10-M9Ej%KFcGEf8$Hmy+yBNE~s;t_XlEo*o2UOnZEU8^M!sOAx2r(Fiwh zARYkWzFzqMd;E+T<2SAN`}pA?;lyzHP@Hln6g>F=*jgw^p)u3GR9wid2P-I*)v`dl z!qHH`vO`(RS~xmFQw{gp!e0)5)G#&&X5I*(F~ej1101;c4ZC$i6ylD2GS8PCK;VI( zhRL00?SF##!QJnieM{~jSfbUmWd7YSSR2d6CJAxF{{g#M5g-uJTi3fwfhGq4--Vg% zUFrNkK$x{GtQ;(C;TjfB@GY5x^qu2YrHVVi6$CQH40ZRq{}Zl^g)4mP>4TtbKOjjB z0E`#tXkj*o13`@VTN1UV>wjno+-s|h1TvwY{s7aS*hl}4;SMs(P4?8_G5<96I1FZQ zwsFW#xRBFyhEz-*>~wRtfZkGuwHs0!AS(>@->t#StNn5TP-^m@<@Dxh9L51fP6pTr)YIN7{UkRNskZ{OEkMx3^gInr zIM4?vk~08+%Y)4#RQf&=VE)%c-^psxL^ zOPF(yx$F%PFM|#zX215vaa>UUq7CnI$~8j4Jh=idu8J{Fa2^~;|CAXvIqHLWC4;iw zVqwaP#ziq6Odc`FMlfq316~|}Ayrp^Rtns>p`31J)&{xBxc z#rm;eu-^dN|Pv`aSSpcFm+my$pg0A@AJnEY3z#>lUI1oEP4c9KVAD`=4)&$rz z{ZAbD&vpuie}xzjo6zcl3az_r9XP zMLqc^6U-P)#GU*H6YF$0v5-xHRul(vVFK|UGvn&Pk)rbN3QP-)@<3Q`l?@3mezXw+ z(wYSIs_3u5Ln`NDjN{;c)q3g{NRUrr$zhAOvuEm^rd{AZ{p%&~6Kal{)DBAV8u7x3C5kKeL{Ns{`XWn*t5c!s`GFOxmxjI6wz1`uxqM11A943)cpmA3v_guq~JWAZ6LfhDI-fX$%0+ zdcS}nt?Z1&4I1>+e#OTh=KDKfLZRRvo^iXeAEhi%qR+qtG1t+g<8d3o2Il{k#C4XV zOI~vW7womMIDQLuW8flb(3=L%%E@9&Y@ zt0=jbP>uzVTr=2igXzFnzv703W&W^35!CD<^aQE`ZFs^=fZwg)2DQG(^3&*kFS-29 z-pvYV+RFSEXeT-F z+?d+wLim5f^K7x3YB&=wPaW_o3{psG3}d{z6E`quq2Q+x0;!_h(N=IdJJoG^Mgc&VdsQzy-e(a28A09fT2HGiaz=D@1#_S>K z|8K0#nu;Af_hoZ(IZ&OWAXH&8dX)431{cPTWf9O-q7SgdfK6axiSqv6Sc2HG5Tj&Y zJ^^hx1$5K^L#R`N|2Nj3Le+J4M1VRHY6)%S0~TEtZVmx{p#@FMR7Y17w=rzAlixO? zVBv&t+@emdEamWY27bNq=*BuZRSGv;kWg(Ir=aK`##8~`(guS$0`3$rqUBwLu;spZxnP@0G+qkYLgpo?Ju6S=ryZO{p*AD|2Njv;N=6aL@c1^WQ5bR5Yo1NN{3 z*n=|00C8FqENP;UAd2o;m7>{qw}VNqHhZtPr@&8oVc1zM}UDZzN(_kV*4VaHne zE_D*x?r{*f0Za+j1AcKUpD?O4Yz00vRmqvkd%(I5tN}<76Nq5+{{z}0H>~gDD}r{} zeg)nW)4w>5;R>{|AY|snF1>3DMG?0EQ&+)kVKUY60aq+c>1_eD3QO>c1d@a52TY(B zv;QB^7TXMI(ZL?U0Hha$1k41ibRIXLjTQwSZE->&|K%vVEU4z84zUxY_ZW`y)-rCm zSPbmX;~3>X&DoAxWQnNF%~_7BG3+0Th1Nel}`Yr#awtgb^tdl z<@J6w$g8n)bK8Gvj{>0H2Rf9P+#EWD8v+(?ws8L^YUYSvLOuo5wwUHu&x9)$rk!H} zg2cwiZ)VjBY7m#x8~0qyu;K=U(fGkGxdVJ6D6&bi2=ofC$s!w=!Wy&8YBn3cL#6XQV z#tn6IN+b(C0;Ys=#JUgxvFedFS>XaCtqt6YJO=0)Q`|r|x|Ox*{hKebkm&V++J;d8 z5;IfLJcS!1mdWM8GL>uUT0ko-1MH6<^kOi(_QsaDfo&)-vEV>*6doMW$OGzP2Am{j z>wF%88x9tx{?yn%S`(XX2I3T&JRsGz>-dk)^`+oFuDFfB61<0N)1uf6s#qD6jW9|e zL`(si{ui>qN$R*Gp~7p6<-D(2s5mDuegmKtnA*?g`5%V=2M)6W0*P?7+iC>>k{QL~ zBL7Fwekcdc9NAwogBCoN!LwJmB>u-;z1DX7B)0f{j zVS6qC`k#QewQKi}&vh4a_|`8p=*G3v!Zx`I=#HPagR=g|1r1gg+wJz7olTlz_k9mi z80?eG#s^$3Y(c^G$D#v=)B0n=90<_g;ecLa0A5|UQ84nS_lZ6OfxyWT24nj5KQO?xWg%A%=#@t(OZzPe>n$X%!~=~G18oHs48o@yuoJe1!=0@WHa1Qc&S3Qk z%UI2>4tADUeVipq{Z(XqW_aU-pb?;-n5 w%bH{(t>14Y+el^Oi@bhQL6B~wg5|YdTB?K~0f)hIz@N`@FxW*Z5S(HE1J6J!MgRZ+ diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index affeba63d1..00d0a8cc20 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -34,20 +34,17 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import net.osmand.AndroidUtils; -import net.osmand.PlatformUtil; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.Amenity; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadRect; import net.osmand.osm.PoiCategory; -import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; -import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard; @@ -56,7 +53,6 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard; import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask; import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard; import net.osmand.plus.mapcontextmenu.controllers.TransportStopController; -import net.osmand.plus.osmedit.utils.SecUtils; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RenderingIcons; import net.osmand.plus.transport.TransportStopRoute; @@ -66,9 +62,7 @@ import net.osmand.plus.widgets.TextViewEx; import net.osmand.plus.widgets.tools.ClickableSpanTouchListener; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; -import org.apache.commons.logging.Log; -import java.io.FileNotFoundException; -import java.io.InputStream; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -83,7 +77,7 @@ public class MenuBuilder { public static final float SHADOW_HEIGHT_TOP_DP = 17f; public static final int TITLE_LIMIT = 60; - protected static final String[] arrowChars = new String[] {"=>", " - "}; + protected static final String[] arrowChars = new String[]{"=>"," - "}; protected MapActivity mapActivity; protected MapContextMenu mapContextMenu; @@ -109,8 +103,6 @@ public class MenuBuilder { private String preferredMapLang; private String preferredMapAppLang; private boolean transliterateNames; - private static final int PICK_IMAGE = 1231; - private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); public interface CollapseExpandListener { void onCollapseExpand(boolean collapsed); @@ -220,59 +212,10 @@ public class MenuBuilder { if (showOnlinePhotos) { buildNearestPhotosRow(view); } - buildUploadImagesRow(view); buildPluginRows(view); // buildAfter(view); } - public void buildUploadImagesRow(View view) { - if (mapContextMenu != null) { - //TODO to strings - String title = "Upload images"; - buildRow(view, R.drawable.ic_action_note_dark, null, title, 0, false, - null, false, 0, false, new OnClickListener() { - @Override - public void onClick(View view) { - mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, - new ActivityResultListener.OnActivityResultListener() { - @Override - public void onResult(int resultCode, Intent resultData) { - InputStream inputStream = null; - try { - inputStream = mapActivity.getContentResolver().openInputStream(resultData.getData()); - } catch (FileNotFoundException e) { - LOG.error(e); - } - handleSelectedImage(inputStream); - } - })); - Intent intent = new Intent(); - intent.setType("image/*"); - intent.setAction(Intent.ACTION_GET_CONTENT); - mapActivity.startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE); - } - }, false); - } - } - - private void handleSelectedImage(final InputStream image) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try{ - String url = "https://test.openplacereviews.org/api/ipfs/image"; - String response = NetworkUtils.sendPostDataRequest(url, image); - //TODO - (new SecUtils()).main(new String[0]); - } - catch (Exception e){ - e.printStackTrace(); - } - } - }); - t.start(); - } - private boolean showTransportRoutes() { return showLocalTransportRoutes() || showNearbyTransportRoutes(); } @@ -311,7 +254,7 @@ public class MenuBuilder { protected boolean needBuildPlainMenuItems() { return true; } - + protected boolean needBuildCoordinatesRow() { return true; } @@ -339,7 +282,7 @@ public class MenuBuilder { protected void buildNearestWikiRow(View view) { if (processNearestWiki() && nearestWiki.size() > 0) { - buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", 0, + buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size()+")", 0, true, getCollapsableWikiView(view.getContext(), true), false, 0, false, null, false); } @@ -379,9 +322,9 @@ public class MenuBuilder { locationData.remove(PointDescription.LOCATION_LIST_HEADER); CollapsableView cv = getLocationCollapsableView(locationData); buildRow(view, R.drawable.ic_action_get_my_location, null, title, 0, true, cv, false, 1, - false, null, false); + false, null, false); } - + private void startLoadingImages() { if (onlinePhotoCardsRow == null) { return; @@ -436,7 +379,7 @@ public class MenuBuilder { } } - protected void buildDescription(View view) { + protected void buildDescription(View view){ } protected void buildAfter(View view) { @@ -452,8 +395,8 @@ public class MenuBuilder { } public View buildRow(View view, int iconId, String buttonText, String text, int textColor, - boolean collapsable, final CollapsableView collapsableView, - boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { + boolean collapsable, final CollapsableView collapsableView, + boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { return buildRow(view, iconId == 0 ? null : getRowIcon(iconId), buttonText, text, textColor, null, collapsable, collapsableView, needLinks, textLinesLimit, isUrl, onClickListener, matchWidthDivider); } @@ -537,7 +480,7 @@ public class MenuBuilder { textPrefixView.setLayoutParams(llTextParams); textPrefixView.setTypeface(FontCache.getRobotoRegular(view.getContext())); textPrefixView.setTextSize(12); - textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); + textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); textPrefixView.setMinLines(1); textPrefixView.setMaxLines(1); textPrefixView.setText(textPrefix); @@ -583,7 +526,7 @@ public class MenuBuilder { textViewSecondary.setLayoutParams(llTextSecondaryParams); textViewSecondary.setTypeface(FontCache.getRobotoRegular(view.getContext())); textViewSecondary.setTextSize(14); - textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); + textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); textViewSecondary.setText(secondaryText); llText.addView(textViewSecondary); } @@ -638,7 +581,7 @@ public class MenuBuilder { } if (collapsableView.getContentView().getParent() != null) { ((ViewGroup) collapsableView.getContentView().getParent()) - .removeView(collapsableView.getContentView()); + .removeView(collapsableView.getContentView()); } baseView.addView(collapsableView.getContentView()); } @@ -798,8 +741,8 @@ public class MenuBuilder { } public void addPlainMenuItem(int iconId, String text, boolean needLinks, boolean isUrl, - boolean collapsable, CollapsableView collapsableView, - OnClickListener onClickListener) { + boolean collapsable, CollapsableView collapsableView, + OnClickListener onClickListener) { plainMenuItems.add(new PlainMenuItem(iconId, null, text, needLinks, isUrl, collapsable, collapsableView, onClickListener)); } @@ -1021,7 +964,7 @@ public class MenuBuilder { button.setTypeface(FontCache.getRobotoRegular(context)); int bg; if (selected) { - bg = light ? R.drawable.context_menu_controller_bg_light_selected : R.drawable.context_menu_controller_bg_dark_selected; + bg = light ? R.drawable.context_menu_controller_bg_light_selected: R.drawable.context_menu_controller_bg_dark_selected; } else if (showAll) { bg = light ? R.drawable.context_menu_controller_bg_light_show_all : R.drawable.context_menu_controller_bg_dark_show_all; } else { @@ -1078,7 +1021,7 @@ public class MenuBuilder { private List getAmenities(QuadRect rect, PoiUIFilter wikiPoiFilter) { return wikiPoiFilter.searchAmenities(rect.top, rect.left, - rect.bottom, rect.right, -1, null); + rect.bottom, rect.right, -1, null); } @SuppressWarnings("unchecked") diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java index 6e38a115ea..393dd7d965 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java @@ -18,7 +18,6 @@ import net.osmand.osm.edit.Entity.EntityType; import net.osmand.osm.edit.EntityInfo; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; -import net.osmand.osm.io.Base64; import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.plus.OsmandApplication; @@ -31,8 +30,10 @@ import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.*; -import java.net.HttpURLConnection; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.HashMap; @@ -107,16 +108,14 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { boolean doAuthenticate) { log.info("Sending request " + url); //$NON-NLS-1$ try { - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - if (doAuthenticate) { - if (client.isValidToken()) { - Response response = client.performRequest(url, requestMethod, requestBody); - return response.getBody(); - } else { - return performBasicAuthRequest(url, requestMethod, requestBody, userOperation); - } - } else { - Response response = client.performRequestWithoutAuth(url, requestMethod, requestBody); + if (doAuthenticate){ + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + Response response = client.performRequest(url,requestMethod,requestBody); + return response.getBody(); + } + else { + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody); return response.getBody(); } } catch (NullPointerException e) { @@ -140,64 +139,11 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); - } catch (Exception e) { - log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ - showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) - + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); } return null; } - private String performBasicAuthRequest(String url, String requestMethod, String requestBody, String userOperation) throws IOException { - HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); - connection.setConnectTimeout(15000); - connection.setRequestMethod(requestMethod); - connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$ - StringBuilder responseBody = new StringBuilder(); - String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$ - connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - connection.setDoInput(true); - if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - connection.setDoOutput(true); - connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$ - OutputStream out = connection.getOutputStream(); - if (requestBody != null) { - BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$ - bwr.write(requestBody); - bwr.flush(); - } - out.close(); - } - connection.connect(); - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - String msg = userOperation - + " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$ - log.error(msg); - showWarning(msg); - } else { - log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$ - // populate return fields. - responseBody.setLength(0); - InputStream i = connection.getInputStream(); - if (i != null) { - BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$ - String s; - boolean f = true; - while ((s = in.readLine()) != null) { - if (!f) { - responseBody.append("\n"); //$NON-NLS-1$ - } else { - f = false; - } - responseBody.append(s); - } - } - return responseBody.toString(); - } - return null; - } - public long openChangeSet(String comment) { long id = -1; StringWriter writer = new StringWriter(256); diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java deleted file mode 100644 index 08f1a71cec..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.osmand.plus.osmedit.utils; - -public class FailedVerificationException extends Exception { - - private static final long serialVersionUID = -4936205097177668159L; - - - public FailedVerificationException(Exception e) { - super(e); - } - - - public FailedVerificationException(String msg) { - super(msg); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java deleted file mode 100644 index 74aca61659..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.osmand.plus.osmedit.utils; - -import com.google.gson.*; -import net.osmand.plus.osmedit.utils.ops.OpObject; -import net.osmand.plus.osmedit.utils.ops.OpOperation; - -import java.io.Reader; -import java.lang.reflect.Type; -import java.util.*; - -public class JsonFormatter { - - private Gson gson; - - private Gson gsonOperationHash; - - private Gson gsonFullOutput; - - public JsonFormatter() { - GsonBuilder builder = new GsonBuilder(); - builder.disableHtmlEscaping(); - builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false)); - builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); - builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); - gson = builder.create(); - - builder = new GsonBuilder(); - builder.disableHtmlEscaping(); - builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false, true)); - builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); - builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); - gsonOperationHash = builder.create(); - - builder = new GsonBuilder(); - builder.disableHtmlEscaping(); - builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(true)); - builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(true)); - builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); - gsonFullOutput = builder.create(); - - - } - - public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer> { - - @Override @SuppressWarnings("unchecked") - public TreeMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return (TreeMap) read(json); - } - - public Object read(JsonElement in) { - - if(in.isJsonArray()){ - List list = new ArrayList(); - JsonArray arr = in.getAsJsonArray(); - for (JsonElement anArr : arr) { - list.add(read(anArr)); - } - return list; - }else if(in.isJsonObject()){ - Map map = new TreeMap(); - JsonObject obj = in.getAsJsonObject(); - Set> entitySet = obj.entrySet(); - for(Map.Entry entry: entitySet){ - map.put(entry.getKey(), read(entry.getValue())); - } - return map; - }else if(in.isJsonPrimitive()){ - JsonPrimitive prim = in.getAsJsonPrimitive(); - if(prim.isBoolean()){ - return prim.getAsBoolean(); - }else if(prim.isString()){ - return prim.getAsString(); - }else if(prim.isNumber()){ - Number num = prim.getAsNumber(); - // here you can handle double int/long values - // and return any type you want - // this solution will transform 3.0 float to long values - if(Math.ceil(num.doubleValue()) == num.longValue() && (!num.toString().contains(".") || num.toString().split("\\.")[1].length() <= 1)) - return num.longValue(); - else { - return num.doubleValue(); - } - } - } - return null; - } - } - -// operations to parse / format related - public OpOperation parseOperation(String opJson) { - return gson.fromJson(opJson, OpOperation.class); - } - - public OpObject parseObject(String opJson) { - return gson.fromJson(opJson, OpObject.class); - } - - public Object parseBlock(String opJson) { - throw new UnsupportedOperationException(""); - } - - public JsonElement toJsonElement(Object o) { - return gson.toJsonTree(o); - } - - @SuppressWarnings("unchecked") - public TreeMap fromJsonToTreeMap(String json) { - return gson.fromJson(json, TreeMap.class); - } - - - public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException { - return gson.fromJson(json, classOfT); - } - - public String fullObjectToJson(Object o) { - return gsonFullOutput.toJson(o); - } - - - public String opToJsonNoHash(OpOperation op) { - return gsonOperationHash.toJson(op); - } - - public String opToJson(OpOperation op) { - return gson.toJson(op); - } - - public String objToJson(OpObject op) { - return gson.toJson(op); - } - - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java deleted file mode 100644 index d0f0765bb6..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java +++ /dev/null @@ -1,411 +0,0 @@ -package net.osmand.plus.osmedit.utils; - - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.*; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -//import java.util.Base64; -import java.util.concurrent.ThreadLocalRandom; - -import net.osmand.plus.osmedit.utils.ops.OpOperation; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.DigestUtils; -//import org.bouncycastle.crypto.generators.SCrypt; -//import org.bouncycastle.crypto.prng.FixedSecureRandom; -public class SecUtils { - private static final String SIG_ALGO_SHA1_EC = "SHA1withECDSA"; - private static final String SIG_ALGO_NONE_EC = "NonewithECDSA"; - - public static final String SIG_ALGO_ECDSA = "ECDSA"; - public static final String ALGO_EC = "EC"; - public static final String EC_256SPEC_K1 = "secp256k1"; - - public static final String KEYGEN_PWD_METHOD_1 = "EC256K1_S17R8"; - public static final String DECODE_BASE64 = "base64"; - public static final String HASH_SHA256 = "sha256"; - public static final String HASH_SHA1 = "sha1"; - - public static final String JSON_MSG_TYPE = "json"; - public static final String KEY_BASE64 = DECODE_BASE64; - - public static void main(String[] args) { - //1) create op, 2) sign op 3) send to server process op - // - KeyPairGenerator keyGen = null ; - SecureRandom random = null; - try { - keyGen = KeyPairGenerator.getInstance(ALGO_EC); - random = SecureRandom.getInstance("SHA1PRNG"); - keyGen.initialize(1024, random); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - KeyPair kp = null; - try { - kp = SecUtils.getKeyPair(ALGO_EC, - "base64:PKCS#8:MD4CAQAwEAYHKoZIzj0CAQYFK4EEAAoEJzAlAgEBBCDR+/ByIjTHZgfdnMfP9Ab5s14mMzFX+8DYqUiGmf/3rw==" - , "base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEOMUiRZwU7wW8L3A1qaJPwhAZy250VaSxJmKCiWdn9EMeubXQgWNT8XUWLV5Nvg7O3sD+1AAQLG5kHY8nOc/AyA=="); - } catch (FailedVerificationException e) { - e.printStackTrace(); - } -// KeyPair kp = generateECKeyPairFromPassword(KEYGEN_PWD_METHOD_1, "openplacereviews", ""); -// KeyPair kp = generateRandomEC256K1KeyPair(); - System.out.println(kp.getPrivate().getFormat()); - System.out.println(kp.getPrivate().getAlgorithm()); - try { - System.out.println(SecUtils.validateKeyPair(ALGO_EC, kp.getPrivate(), kp.getPublic())); - } catch (FailedVerificationException e) { - e.printStackTrace(); - } - String pr = encodeKey(KEY_BASE64, kp.getPrivate()); - String pk = encodeKey(KEY_BASE64, kp.getPublic()); - String algo = kp.getPrivate().getAlgorithm(); - System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", kp.getPrivate().getFormat(), pr, kp - .getPublic().getFormat(), pk)); - String signMessageTest = "Hello this is a registration message test"; - byte[] signature = signMessageWithKey(kp, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC); - System.out.println(String.format("Signed message: %s %s", android.util.Base64.decode(signature, android.util.Base64.DEFAULT), - signMessageTest)); - - KeyPair nk = null; - try { - nk = getKeyPair(algo, pr, pk); - } catch (FailedVerificationException e) { - e.printStackTrace(); - } - // validate - pr = new String(android.util.Base64.decode(nk.getPrivate().getEncoded(), android.util.Base64.DEFAULT)); - pk = new String(android.util.Base64.decode(nk.getPublic().getEncoded(), android.util.Base64.DEFAULT)); - - System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", nk.getPrivate().getFormat(), pr, nk - .getPublic().getFormat(), pk)); - System.out.println(validateSignature(nk, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC, signature)); - - JsonFormatter formatter = new JsonFormatter(); - String msg = "{\n" + - " \"type\" : \"sys.signup\",\n" + - " \"signed_by\": \"openplacereviews\",\n" + - " \"create\": [{\n" + - " \"id\": [\"openplacereviews\"],\n" + - " \"name\" : \"openplacereviews\",\n" + - " \"algo\": \"EC\",\n" + - " \"auth_method\": \"provided\",\n" + - " \"pubkey\": \"base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEn6GkOTN3SYc+OyCYCpqPzKPALvUgfUVNDJ+6eyBlCHI1/gKcVqzHLwaO90ksb29RYBiF4fW/PqHcECNzwJB+QA==\"\n" + - " }]\n" + - " }"; - - OpOperation opOperation = formatter.parseOperation(msg); - String hash = JSON_MSG_TYPE + ":" - + SecUtils.calculateHashWithAlgo(SecUtils.HASH_SHA256, null, - formatter.opToJsonNoHash(opOperation)); - - byte[] hashBytes = SecUtils.getHashBytes(hash); - String signatureTxt = SecUtils.signMessageWithKeyBase64(kp, hashBytes, SecUtils.SIG_ALGO_ECDSA, null); - System.out.println(formatter.opToJsonNoHash(opOperation)); - System.out.println(hash); - System.out.println(signatureTxt); - } - - public static EncodedKeySpec decodeKey(String key) { - if (key.startsWith(KEY_BASE64 + ":")) { - key = key.substring(KEY_BASE64.length() + 1); - int s = key.indexOf(':'); - if (s == -1) { - throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); - } - return getKeySpecByFormat(key.substring(0, s), - android.util.Base64.decode(key.substring(s + 1), android.util.Base64.DEFAULT)); - } - throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); - } - - public static String encodeKey(String algo, PublicKey pk) { - if (algo.equals(KEY_BASE64)) { - return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); - } - throw new UnsupportedOperationException("Algorithm is not supported: " + algo); - } - - public static String encodeKey(String algo, PrivateKey pk) { - if (algo.equals(KEY_BASE64)) { - return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); - } - throw new UnsupportedOperationException("Algorithm is not supported: " + algo); - } - - public static EncodedKeySpec getKeySpecByFormat(String format, byte[] data) { - switch (format) { - case "PKCS#8": - return new PKCS8EncodedKeySpec(data); - case "X.509": - return new X509EncodedKeySpec(data); - } - throw new IllegalArgumentException(format); - } - - public static String encodeBase64(byte[] data) { - return new String(android.util.Base64.decode(data, android.util.Base64.DEFAULT)); - } - - public static boolean validateKeyPair(String algo, PrivateKey privateKey, PublicKey publicKey) - throws FailedVerificationException { - if (!algo.equals(ALGO_EC)) { - throw new FailedVerificationException("Algorithm is not supported: " + algo); - } - // create a challenge - byte[] challenge = new byte[512]; - ThreadLocalRandom.current().nextBytes(challenge); - - try { - // sign using the private key - Signature sig = Signature.getInstance(SIG_ALGO_SHA1_EC); - sig.initSign(privateKey); - sig.update(challenge); - byte[] signature = sig.sign(); - - // verify signature using the public key - sig.initVerify(publicKey); - sig.update(challenge); - - boolean keyPairMatches = sig.verify(signature); - return keyPairMatches; - } catch (InvalidKeyException e) { - throw new FailedVerificationException(e); - } catch (NoSuchAlgorithmException e) { - throw new FailedVerificationException(e); - } catch (SignatureException e) { - throw new FailedVerificationException(e); - } - } - - public static KeyPair getKeyPair(String algo, String prKey, String pbKey) throws FailedVerificationException { - try { - KeyFactory keyFactory = KeyFactory.getInstance(algo); - PublicKey pb = null; - PrivateKey pr = null; - if (pbKey != null) { - pb = keyFactory.generatePublic(decodeKey(pbKey)); - } - if (prKey != null) { - pr = keyFactory.generatePrivate(decodeKey(prKey)); - } - return new KeyPair(pb, pr); - } catch (NoSuchAlgorithmException e) { - throw new FailedVerificationException(e); - } catch (InvalidKeySpecException e) { - throw new FailedVerificationException(e); - } - } - - public static KeyPair generateKeyPairFromPassword(String algo, String keygenMethod, String salt, String pwd) - throws FailedVerificationException { - if (algo.equals(ALGO_EC)) { - return generateECKeyPairFromPassword(keygenMethod, salt, pwd); - } - throw new UnsupportedOperationException("Unsupported algo keygen method: " + algo); - } - - public static KeyPair generateECKeyPairFromPassword(String keygenMethod, String salt, String pwd) - throws FailedVerificationException { - if (keygenMethod.equals(KEYGEN_PWD_METHOD_1)) { - return generateEC256K1KeyPairFromPassword(salt, pwd); - } - throw new UnsupportedOperationException("Unsupported keygen method: " + keygenMethod); - } - - // "EC:secp256k1:scrypt(salt,N:17,r:8,p:1,len:256)" algorithm - EC256K1_S17R8 - public static KeyPair generateEC256K1KeyPairFromPassword(String salt, String pwd) - throws FailedVerificationException { - try { - KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); - ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); - if (pwd.length() < 10) { - throw new IllegalArgumentException("Less than 10 characters produces only 50 bit entropy"); - } - byte[] bytes = pwd.getBytes("UTF-8"); - //byte[] scrypt = SCrypt.generate(bytes, salt.getBytes("UTF-8"), 1 << 17, 8, 1, 256); - //kpg.initialize(ecSpec, new FixedSecureRandom(scrypt)); - return kpg.genKeyPair(); - } catch (NoSuchAlgorithmException e) { - throw new FailedVerificationException(e); - } catch (UnsupportedEncodingException e) { - throw new FailedVerificationException(e); - } /* catch (InvalidAlgorithmParameterException e) { - throw new FailedVerificationException(e); - }*/ - } - - public static KeyPair generateRandomEC256K1KeyPair() throws FailedVerificationException { - try { - KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); - ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); - kpg.initialize(ecSpec); - return kpg.genKeyPair(); - } catch (NoSuchAlgorithmException e) { - throw new FailedVerificationException(e); - } catch (InvalidAlgorithmParameterException e) { - throw new FailedVerificationException(e); - } - } - - public static String signMessageWithKeyBase64(KeyPair keyPair, byte[] msg, String signAlgo, ByteArrayOutputStream out) { - byte[] sigBytes = signMessageWithKey(keyPair, msg, signAlgo); - if(out != null) { - try { - out.write(sigBytes); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - String signature = new String(android.util.Base64.decode(sigBytes, android.util.Base64.DEFAULT)); - return signAlgo + ":" + DECODE_BASE64 + ":" + signature; - } - - public static byte[] signMessageWithKey(KeyPair keyPair, byte[] msg, String signAlgo) { - try { - Signature sig = Signature.getInstance(getInternalSigAlgo(signAlgo)); - sig.initSign(keyPair.getPrivate()); - sig.update(msg); - byte[] signatureBytes = sig.sign(); - return signatureBytes; - } catch (NoSuchAlgorithmException e) { - //throw new FailedVerificationException(e); - } catch (InvalidKeyException e) { - //throw new FailedVerificationException(e); - } catch (SignatureException e) { - //throw new FailedVerificationException(e); - } - return new byte[0]; - } - - public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sig) { - if(sig == null || keyPair == null) { - return false; - } - int ind = sig.indexOf(':'); - String sigAlgo = sig.substring(0, ind); - return validateSignature(keyPair, msg, sigAlgo, decodeSignature(sig.substring(ind + 1))); - } - - public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sigAlgo, byte[] signature) { - if (keyPair == null) { - return false; - } - try { - Signature sig = Signature.getInstance(getInternalSigAlgo(sigAlgo)); - sig.initVerify(keyPair.getPublic()); - sig.update(msg); - return sig.verify(signature); - } catch (NoSuchAlgorithmException e) { - //throw new FailedVerificationException(e); - } catch (InvalidKeyException e) { - //throw new FailedVerificationException(e); - } catch (SignatureException e) { - //throw new FailedVerificationException(e); - } - return false; - } - - private static String getInternalSigAlgo(String sigAlgo) { - return sigAlgo.equals(SIG_ALGO_ECDSA)? SIG_ALGO_NONE_EC : sigAlgo; - } - - - public static byte[] calculateHash(String algo, byte[] b1, byte[] b2) { - byte[] m = mergeTwoArrays(b1, b2); - if (algo.equals(HASH_SHA256)) { - return DigestUtils.sha256(m); - } else if (algo.equals(HASH_SHA1)) { - return DigestUtils.sha1(m); - } - throw new UnsupportedOperationException(); - } - - public static byte[] mergeTwoArrays(byte[] b1, byte[] b2) { - byte[] m = b1 == null ? b2 : b1; - if(b2 != null && b1 != null) { - m = new byte[b1.length + b2.length]; - System.arraycopy(b1, 0, m, 0, b1.length); - System.arraycopy(b2, 0, m, b1.length, b2.length); - } - return m; - } - - public static String calculateHashWithAlgo(String algo, String salt, String msg) { - try { - String hex = Hex.encodeHexString(calculateHash(algo, salt == null ? null : salt.getBytes("UTF-8"), - msg == null ? null : msg.getBytes("UTF-8"))); - return algo + ":" + hex; - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } - } - - public static String calculateHashWithAlgo(String algo, byte[] bts) { - byte[] hash = calculateHash(algo, bts, null); - return formatHashWithAlgo(algo, hash); - } - - public static String formatHashWithAlgo(String algo, byte[] hash) { - String hex = Hex.encodeHexString(hash); - return algo + ":" + hex; - } - - public static byte[] getHashBytes(String msg) { - if(msg == null || msg.length() == 0) { - // special case for empty hash - return new byte[0]; - } - int i = msg.lastIndexOf(':'); - String s = i >= 0 ? msg.substring(i + 1) : msg; - try { - return Hex.decodeHex(s); - } catch (DecoderException e) { - throw new IllegalArgumentException(e); - } - } - - - public static boolean validateHash(String hash, String salt, String msg) { - int s = hash.indexOf(":"); - if (s == -1) { - throw new IllegalArgumentException(String.format("Hash %s doesn't contain algorithm of hashing to verify", - s)); - } - String v = calculateHashWithAlgo(hash.substring(0, s), salt, msg); - return hash.equals(v); - } - - public static byte[] decodeSignature(String digest) { - try { - int indexOf = digest.indexOf(DECODE_BASE64 + ":"); - if (indexOf != -1) { -// return Base64.getDecoder().decode(digest.substring(indexOf + DECODE_BASE64.length() + 1). -// getBytes("UTF-8")); - return android.util.Base64.decode(digest.substring(indexOf + DECODE_BASE64.length() + 1) - .getBytes("UTF-8"), android.util.Base64.DEFAULT); - } - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } - throw new IllegalArgumentException("Unknown format for signature " + digest); - } - - public static String hexify(byte[] bytes) { - if(bytes == null || bytes.length == 0) { - return ""; - } - return Hex.encodeHexString(bytes); - - } - - - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java deleted file mode 100644 index 20350f6572..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java +++ /dev/null @@ -1,530 +0,0 @@ -package net.osmand.plus.osmedit.utils.ops; - -import com.google.gson.*; -import net.osmand.plus.osmedit.utils.util.JsonObjectUtils; -import net.osmand.plus.osmedit.utils.util.OUtils; - -import java.lang.reflect.Type; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; - -public class OpObject { - - public static final String F_NAME = "name"; - public static final String F_ID = "id"; - public static final String F_COMMENT = "comment"; - public static final String TYPE_OP = "sys.op"; - public static final String TYPE_BLOCK = "sys.block"; - public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; - // transient info about validation timing etc - public static final String F_EVAL = "eval"; - public static final String F_VALIDATION = "validation"; - public static final String F_TIMESTAMP_ADDED = "timestamp"; - public static final String F_PARENT_TYPE = "parentType"; - public static final String F_PARENT_HASH = "parentHash"; - public static final String F_CHANGE = "change"; - public static final String F_CURRENT = "current"; - // voting - public static final String F_OP = "op"; - public static final String F_STATE = "state"; - public static final String F_OPEN = "open"; - public static final String F_FINAL = "final"; - public static final String F_VOTE = "vote"; - public static final String F_VOTES = "votes"; - public static final String F_SUBMITTED_OP_HASH = "submittedOpHash"; - public static final String F_USER = "user"; - - public static final OpObject NULL = new OpObject(true); - - public static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); - static { - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - protected Map fields = new TreeMap<>(); - protected transient Map cacheFields; - protected boolean isImmutable; - - protected transient String parentType; - protected transient String parentHash; - protected transient boolean deleted; - - - public OpObject() {} - - public OpObject(boolean deleted) { - this.deleted = deleted; - } - - public OpObject(OpObject cp) { - this(cp, false); - } - - public OpObject(OpObject cp, boolean copyCacheFields) { - createOpObjectCopy(cp, copyCacheFields); - } - - @SuppressWarnings("unchecked") - private OpObject createOpObjectCopy(OpObject opObject, boolean copyCacheFields) { - this.parentType = opObject.parentType; - this.parentHash = opObject.parentHash; - this.deleted = opObject.deleted; - this.fields = (Map) copyingObjects(opObject.fields, copyCacheFields); - if (opObject.cacheFields != null && copyCacheFields) { - this.cacheFields = (Map) copyingObjects(opObject.cacheFields, copyCacheFields); - } - this.isImmutable = false; - - return this; - } - - public boolean isDeleted() { - return deleted; - } - - @SuppressWarnings("unchecked") - private Object copyingObjects(Object object, boolean copyCacheFields) { - if (object instanceof Number) { - return (Number) object; - } else if (object instanceof String) { - return (String) object; - } else if (object instanceof Boolean) { - return (Boolean) object; - } else if (object instanceof List) { - List copy = new ArrayList<>(); - List list = (List) object; - for (Object o : list) { - copy.add(copyingObjects(o, copyCacheFields)); - } - return copy; - } else if (object instanceof Map) { - Map copy = new LinkedHashMap<>(); - Map map = (Map) object; - for (Object o : map.keySet()) { - copy.put(o, copyingObjects(map.get(o), copyCacheFields)); - } - return copy; - } else if (object instanceof OpObject) { - return new OpObject((OpObject) object); - } else { - throw new UnsupportedOperationException("Type of object is not supported"); - } - } - - public void setParentOp(OpOperation op) { - setParentOp(op.type, op.getRawHash()); - } - - public void setParentOp(String parentType, String parentHash) { - this.parentType = parentType; - this.parentHash = parentHash; - } - - public String getParentHash() { - return parentHash; - } - - public String getParentType() { - return parentType; - } - - public List getId() { - return getStringList(F_ID); - } - - public void setId(String id) { - addOrSetStringValue(F_ID, id);; - } - - public boolean isImmutable() { - return isImmutable; - } - - public OpObject makeImmutable() { - isImmutable = true; - return this; - } - - public Object getFieldByExpr(String field) { - if (field.contains(".") || field.contains("[") || field.contains("]")) { - return JsonObjectUtils.getField(this.fields, generateFieldSequence(field)); - } - - return fields.get(field); - } - - - /** - * generateFieldSequence("a") - [a] - * generateFieldSequence("a.b") - [a, b] - * generateFieldSequence("a.b.c.de") - [a, b, c, de] - * generateFieldSequence("a.bwerq.c") - [a, bwerq, c] - * generateFieldSequence("a.bwerq...c") - [a, bwerq, c] - * generateFieldSequence("a.bwereq..c..") - [a, bwerq, c] - * generateFieldSequence("a.{b}") - [a, b] - * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] - * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] - * generateFieldSequence("a.{b{}}") - [a, b{}] - * generateFieldSequence("a.{b{}d.q}") - [a, b{}d.q] - */ - private static List generateFieldSequence(String field) { - int STATE_OPEN_BRACE = 1; - int STATE_OPEN = 0; - int state = STATE_OPEN; - int start = 0; - List l = new ArrayList(); - for(int i = 0; i < field.length(); i++) { - boolean split = false; - if (i == field.length() - 1) { - if (state == STATE_OPEN_BRACE) { - if(field.charAt(i) == '}') { - split = true; - } else { - throw new IllegalArgumentException("Illegal field expression: " + field); - } - } else { - if(field.charAt(i) != '.') { - i++; - } - split = true; - } - } else { - if (field.charAt(i) == '.' && state == STATE_OPEN) { - split = true; - } else if (field.charAt(i) == '}' && field.charAt(i + 1) == '.' && state == STATE_OPEN_BRACE) { - split = true; - } else if (field.charAt(i) == '{' && state == STATE_OPEN) { - if(start != i) { - throw new IllegalArgumentException("Illegal field expression (wrap {} is necessary): " + field); - } - state = STATE_OPEN_BRACE; - start = i + 1; - } - } - if(split) { - if (i != start) { - l.add(field.substring(start, i)); - } - start = i + 1; - state = STATE_OPEN; - } - } - return l; - } - - public void setFieldByExpr(String field, Object object) { - if (field.contains(".") || field.contains("[") || field.contains("]")) { - List fieldSequence = generateFieldSequence(field); - if (object == null) { - JsonObjectUtils.deleteField(this.fields, fieldSequence); - } else { - JsonObjectUtils.setField(this.fields, fieldSequence, object); - } - } else if (object == null) { - fields.remove(field); - } else { - fields.put(field, object); - } - } - - - public Object getCacheObject(String f) { - if(cacheFields == null) { - return null; - } - return cacheFields.get(f); - } - - public void putCacheObject(String f, Object o) { - if (isImmutable()) { - if (cacheFields == null) { - cacheFields = new ConcurrentHashMap(); - } - cacheFields.put(f, o); - } - } - - public void setId(String id, String id2) { - List list = new ArrayList(); - list.add(id); - list.add(id2); - putObjectValue(F_ID, list); - } - - public String getName() { - return getStringValue(F_NAME); - } - - public String getComment() { - return getStringValue(F_COMMENT); - } - - public Map getRawOtherFields() { - return fields; - } - - @SuppressWarnings("unchecked") - public Map getStringMap(String field) { - return (Map) fields.get(field); - } - - @SuppressWarnings("unchecked") - public Map> getMapStringList(String field) { - return (Map>) fields.get(field); - } - - @SuppressWarnings("unchecked") - public List> getListStringMap(String field) { - return (List>) fields.get(field); - } - - @SuppressWarnings("unchecked") - public List> getListStringObjMap(String field) { - return (List>) fields.get(field); - } - - @SuppressWarnings("unchecked") - public Map getStringObjMap(String field) { - return (Map) fields.get(field); - } - - @SuppressWarnings("unchecked") - public T getField(T def, String... fields) { - Map p = this.fields; - for(int i = 0; i < fields.length - 1 ; i++) { - p = (Map) p.get(fields[i]); - if(p == null) { - return def; - } - } - T res = (T) p.get(fields[fields.length - 1]); - if(res == null) { - return def; - } - return res; - } - - @SuppressWarnings("unchecked") - public Map, Object> getStringListObjMap(String field) { - return (Map, Object>) fields.get(field); - } - - - public long getDate(String field) { - String date = getStringValue(field); - if(OUtils.isEmpty(date)) { - return 0; - } - try { - return dateFormat.parse(date).getTime(); - } catch (ParseException e) { - return 0; - } - } - - - public void setDate(String field, long time) { - putStringValue(field, dateFormat.format(new Date(time))); - } - - public Number getNumberValue(String field) { - return (Number) fields.get(field); - } - - public int getIntValue(String key, int def) { - Number o = getNumberValue(key); - return o == null ? def : o.intValue(); - } - - public long getLongValue(String key, long def) { - Number o = getNumberValue(key); - return o == null ? def : o.longValue(); - } - - public String getStringValue(String field) { - Object o = fields.get(field); - if (o instanceof String || o == null) { - return (String) o; - } - return o.toString(); - } - - @SuppressWarnings("unchecked") - public List getStringList(String field) { - // cast to list if it is single value - Object o = fields.get(field); - if(o == null || o.toString().isEmpty()) { - return Collections.emptyList(); - } - if(o instanceof String) { - return Collections.singletonList(o.toString()); - } - return (List) o; - } - - public Object getObjectValue(String field) { - return fields.get(field); - } - - public void putStringValue(String key, String value) { - checkNotImmutable(); - if(value == null) { - fields.remove(key); - } else { - fields.put(key, value); - } - } - - /** - * Operates as a single value if cardinality is less than 1 - * or as a list of values if it stores > 1 value - * @param key - * @param value - */ - @SuppressWarnings("unchecked") - public void addOrSetStringValue(String key, String value) { - checkNotImmutable(); - Object o = fields.get(key); - if(o == null) { - fields.put(key, value); - } else if(o instanceof List) { - ((List) o).add(value); - } else { - List list = new ArrayList(); - list.add(o.toString()); - list.add(value); - fields.put(key, list); - } - } - - @SuppressWarnings("unchecked") - public Map getChangedEditFields() { - return (Map) fields.get(F_CHANGE); - } - - @SuppressWarnings("unchecked") - public Map getCurrentEditFields() { - return (Map) fields.get(F_CURRENT); - } - - public void putObjectValue(String key, Object value) { - checkNotImmutable(); - if(value == null) { - fields.remove(key); - } else { - fields.put(key, value); - } - } - - public void checkNotImmutable() { - if(isImmutable) { - throw new IllegalStateException("Object is immutable"); - } - - } - - public void checkImmutable() { - if(!isImmutable) { - throw new IllegalStateException("Object is mutable"); - } - } - - public Object remove(String key) { - checkNotImmutable(); - return fields.remove(key); - } - - public Map getMixedFieldsAndCacheMap() { - TreeMap mp = new TreeMap<>(fields); - if(cacheFields != null || parentType != null || parentHash != null) { - TreeMap eval = new TreeMap(); - - if(parentType != null) { - eval.put(F_PARENT_TYPE, parentType); - } - if(parentHash != null) { - eval.put(F_PARENT_HASH, parentHash); - } - if (cacheFields != null) { - Iterator> it = cacheFields.entrySet().iterator(); - while (it.hasNext()) { - Entry e = it.next(); - Object v = e.getValue(); - if (v instanceof Map || v instanceof String || v instanceof Number) { - eval.put(e.getKey(), v); - } - } - } - if(eval.size() > 0) { - mp.put(F_EVAL, eval); - } - } - return mp; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((fields == null) ? 0 : fields.hashCode()); - return result; - } - - @Override - public String toString() { - return getClass().getSimpleName() + "[" + fields + "]"; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - OpObject other = (OpObject) obj; - if (fields == null) { - if (other.fields != null) - return false; - } else if (!fields.equals(other.fields)) - return false; - return true; - } - - public static class OpObjectAdapter implements JsonDeserializer, - JsonSerializer { - - private boolean fullOutput; - - public OpObjectAdapter(boolean fullOutput) { - this.fullOutput = fullOutput; - } - - @Override - public OpObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - OpObject bn = new OpObject(); - bn.fields = context.deserialize(json, TreeMap.class); - // remove cache - bn.fields.remove(F_EVAL); - return bn; - } - - @Override - public JsonElement serialize(OpObject src, Type typeOfSrc, JsonSerializationContext context) { - return context.serialize(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); - } - - - } - - - - - - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java deleted file mode 100644 index ad1c9571ae..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java +++ /dev/null @@ -1,281 +0,0 @@ -package net.osmand.plus.osmedit.utils.ops; - -import com.google.gson.*; - -import java.lang.reflect.Type; -import java.util.*; - -public class OpOperation extends OpObject { - - public static final String F_TYPE = "type"; - public static final String F_SIGNED_BY = "signed_by"; - public static final String F_HASH = "hash"; - - public static final String F_SIGNATURE = "signature"; - - public static final String F_REF = "ref"; - public static final String F_CREATE = "create"; - public static final String F_DELETE = "delete"; - public static final String F_EDIT = "edit"; - - public static final String F_NAME = "name"; - public static final String F_COMMENT = "comment"; - - private List createdObjects = new LinkedList(); - private List editedObjects = new LinkedList(); - protected String type; - - public OpOperation() { - } - - public OpOperation(OpOperation cp, boolean copyCacheFields) { - super(cp, copyCacheFields); - this.type = cp.type; - for(OpObject o : cp.createdObjects) { - this.createdObjects.add(new OpObject(o, copyCacheFields)); - } - for(OpObject o : cp.editedObjects) { - this.editedObjects.add(new OpObject(o, copyCacheFields)); - } - } - - public String getOperationType() { - return type; - } - - public void setType(String name) { - checkNotImmutable(); - type = name; - updateObjectsRef(); - } - - protected void updateObjectsRef() { - for(OpObject o : createdObjects) { - o.setParentOp(this); - } - for(OpObject o : editedObjects) { - o.setParentOp(this); - } - } - - public String getType() { - return type; - } - - public OpOperation makeImmutable() { - isImmutable = true; - for(OpObject o : createdObjects) { - o.makeImmutable(); - } - for(OpObject o : editedObjects) { - o.makeImmutable(); - } - return this; - } - - public void setSignedBy(String value) { - putStringValue(F_SIGNED_BY, value); - } - - public void addOtherSignedBy(String value) { - super.addOrSetStringValue(F_SIGNED_BY, value); - } - - public List getSignedBy() { - return getStringList(F_SIGNED_BY); - } - - public String getHash() { - return getStringValue(F_HASH); - } - - public String getRawHash() { - String rw = getStringValue(F_HASH); - // drop algorithm and everything else - if(rw != null) { - rw = rw.substring(rw.lastIndexOf(':') + 1); - } - return rw; - } - - public List getSignatureList() { - return getStringList(F_SIGNATURE); - } - - public Map> getRef() { - return getMapStringList(F_REF); - } - - @SuppressWarnings("unchecked") - public List> getDeleted() { - List> l = (List>) fields.get(F_DELETE); - if(l == null) { - return Collections.emptyList(); - } - return l; - } - - public boolean hasDeleted() { - return getDeleted().size() > 0; - } - - public void addDeleted(List id) { - if(!fields.containsKey(F_DELETE)) { - ArrayList> lst = new ArrayList<>(); - lst.add(id); - putObjectValue(F_DELETE, lst); - } else { - getDeleted().add(id); - } - } - - public List getCreated() { - return createdObjects; - } - - public void addCreated(OpObject o) { - checkNotImmutable(); - createdObjects.add(o); - if(type != null) { - o.setParentOp(this); - } - } - - public boolean hasCreated() { - return createdObjects.size() > 0; - } - - public void addEdited(OpObject o) { - checkNotImmutable(); - editedObjects.add(o); - if (type != null) { - o.setParentOp(this); - } - } - - public List getEdited() { - return editedObjects; - } - - public boolean hasEdited() { - return editedObjects.size() > 0; - } - - - public String getName() { - return getStringValue(F_NAME); - } - - public String getComment() { - return getStringValue(F_COMMENT); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((createdObjects == null) ? 0 : createdObjects.hashCode()); - result = prime * result + ((editedObjects == null) ? 0 : editedObjects.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - OpOperation other = (OpOperation) obj; - if (createdObjects == null) { - if (other.createdObjects != null) - return false; - } else if (!createdObjects.equals(other.createdObjects)) - return false; - if (editedObjects == null) { - if (other.editedObjects != null) - return false; - } else if (!editedObjects.equals(other.editedObjects)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return true; - } - - - - public static class OpOperationBeanAdapter implements JsonDeserializer, - JsonSerializer { - - // plain serialization to calculate hash - private boolean excludeHashAndSignature; - private boolean fullOutput; - - public OpOperationBeanAdapter(boolean fullOutput, boolean excludeHashAndSignature) { - this.excludeHashAndSignature = excludeHashAndSignature; - this.fullOutput = fullOutput; - } - - public OpOperationBeanAdapter(boolean fullOutput) { - this.fullOutput = fullOutput; - this.excludeHashAndSignature = false; - } - - @Override - public OpOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - JsonObject jsonObj = json.getAsJsonObject(); - OpOperation op = new OpOperation(); - JsonElement tp = jsonObj.remove(F_TYPE); - if(tp != null) { - String opType = tp.getAsString(); - op.type = opType; - } else { - op.type = ""; - } - JsonElement createdObjs = jsonObj.remove(F_CREATE); - if(createdObjs != null) { - JsonArray ar = createdObjs.getAsJsonArray(); - for(int i = 0; i < ar.size(); i++) { - //op.addCreated(context.deserialize(ar.get(i), OpObject.class)); - } - } - - JsonElement editedObjs = jsonObj.remove(F_EDIT); - if (editedObjs != null) { - for (JsonElement editElem : editedObjs.getAsJsonArray()) { - //op.addEdited(context.deserialize(editElem, OpObject.class)); - } - } - - jsonObj.remove(F_EVAL); - op.fields = context.deserialize(jsonObj, TreeMap.class); - return op; - } - - @Override - public JsonElement serialize(OpOperation src, Type typeOfSrc, JsonSerializationContext context) { - TreeMap tm = new TreeMap<>(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); - if(excludeHashAndSignature) { - tm.remove(F_SIGNATURE); - tm.remove(F_HASH); - } - tm.put(F_TYPE, src.type); - - if (src.hasEdited()) { - tm.put(F_EDIT, context.serialize(src.editedObjects)); - } - - if(src.hasCreated()) { - tm.put(F_CREATE, context.serialize(src.createdObjects)); - } - return context.serialize(tm); - } - } - -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java deleted file mode 100644 index a6abf266d6..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java +++ /dev/null @@ -1,171 +0,0 @@ -package net.osmand.plus.osmedit.utils.ops; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -public class PerformanceMetrics { - private static final PerformanceMetrics inst = new PerformanceMetrics(); - public static final int METRICS_COUNT = 2; - public static PerformanceMetrics i() { - return inst; - } - private final Map metrics = new ConcurrentHashMap(); - private final AtomicInteger ids = new AtomicInteger(); - private final PerformanceMetric DISABLED = new PerformanceMetric(-1, ""); - private boolean enabled = true; - private PerformanceMetric overhead; - - private PerformanceMetrics() { - overhead = getByKey("_overhead"); - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public Map getMetrics() { - return metrics; - } - - public PerformanceMetric getMetric(String prefix, String key) { - if(!enabled) { - return DISABLED; - } - return getMetric(prefix + "." + key); - } - - public void reset(int c) { - for(PerformanceMetric p : metrics.values()) { - p.reset(c); - } - } - - - public PerformanceMetric getMetric(String key) { - if(!enabled) { - return DISABLED; - } - long s = System.nanoTime(); - PerformanceMetric pm = getByKey(key); - overhead.capture(System.nanoTime() - s); - return pm; - } - - private PerformanceMetric getByKey(String key) { - PerformanceMetric pm = metrics.get(key); - if(pm == null) { - pm = new PerformanceMetric(ids.incrementAndGet(), key); - metrics.put(key, pm); - } - return pm; - } - - - public final class PerformanceMetric { - final String name; - final int id; - String description; - AtomicInteger invocations = new AtomicInteger(); - AtomicLong totalDuration = new AtomicLong(); - AtomicInteger invocationsA = new AtomicInteger(); - AtomicLong totalDurationA = new AtomicLong(); - AtomicInteger invocationsB = new AtomicInteger(); - AtomicLong totalDurationB = new AtomicLong(); - - - - private PerformanceMetric(int id, String name) { - this.id = id; - this.name = name; - } - - public Metric start() { - if(id == -1) { - return Metric.EMPTY; - } - return new Metric(this); - } - - public String getName() { - return name; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - - public void reset(int c) { - if(c == 1) { - invocationsA.set(0); - totalDurationA.set(0); - } else if(c == 2) { - totalDurationB.set(0); - invocationsB.set(0); - } - } - - public int getInvocations(int c) { - if(c == 1) { - return invocationsA.get(); - } else if(c == 2) { - return invocationsB.get(); - } - return invocations.get(); - } - - public long getDuration(int c) { - if(c == 1) { - return totalDurationA.get(); - } else if(c == 2) { - return totalDurationB.get(); - } - return totalDuration.get(); - } - - public int getId() { - return id; - } - - long capture(long d) { - invocations.incrementAndGet(); - totalDuration.addAndGet(d); - invocationsA.incrementAndGet(); - totalDurationA.addAndGet(d); - invocationsB.incrementAndGet(); - totalDurationB.addAndGet(d); - return d; - } - } - - public static final class Metric { - long start; - PerformanceMetric m; - boolean e; - public static final Metric EMPTY = new Metric(true); - private Metric(PerformanceMetric m) { - start = System.nanoTime(); - this.m = m; - } - - private Metric(boolean empty) { - e = empty; - } - - public long capture() { - if(e) { - return 0; - } - return m.capture(System.nanoTime() - start); - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java deleted file mode 100644 index fb769b10c5..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.osmand.plus.osmedit.utils.util; - -public interface DBConstants { - - public static final String SCHEMA_NAME = "public"; - - // Tables - public static final String BLOCK_TABLE = "blocks"; - - public static final String LOGINS_TABLE = "logins"; - - public static final String USERS_TABLE = "users"; - - public static final String QUEUE_TABLE = "queue"; - - public static final String ROLES_TABLE = "roles"; - - public static final String TABLES_TABLE = "tables"; - - public static final String OP_DEFINITIONS_TABLE = "op_definitions"; - - public static final String EXECUTED_OPERATIONS_TABLE = "operations"; - - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java deleted file mode 100644 index e8ccc187f1..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java +++ /dev/null @@ -1,275 +0,0 @@ -package net.osmand.plus.osmedit.utils.util; - -import java.util.*; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Class uses for work with Json Object represent as Map. - */ -public class JsonObjectUtils { - - - private static final int GET_OPERATION = 0; - private static final int SET_OPERATION = 1; - private static final int DELETE_OPERATION = 2; - protected static final Log LOGGER = LogFactory.getLog(JsonObjectUtils.class); - - private static class OperationAccess { - private final int operation; - private final Object value; - - private OperationAccess(int op, Object v) { - this.operation = op; - this.value = v; - } - - } - - /** - * Retrieve value from jsonMap by field sequence. - * @param jsonMap source json object deserialized in map - * @param fieldSequence Sequence to field value. - * Example: person.car.number have to be ["person", "car[2]", "number"] - * @return Field value - */ - public static Object getField(Map jsonMap, String[] fieldSequence) { - return accessField(jsonMap, fieldSequence, new OperationAccess(GET_OPERATION, null)); - } - - /** - * Set value to json field (path to field presented as sequence of string) - * - * @param jsonMap source json object deserialized in map - * @param fieldSequence Sequence to field value. - * * Example: person.car.number have to be ["person", "car[2]", "number"] - * @param field field value - * @return - */ - public static Object setField(Map jsonMap, List fieldSequence, Object field) { - return setField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), field); - } - - /** - * Set value to json field (path to field presented as sequence of string) - * - * @param jsonObject source json object deserialized in map - * @param fieldSequence Sequence to field value. - * * Example: person.car.number have to be ["person", "car[2]", "number"] - * @param field field value - * @return - */ - public static Object setField(Map jsonObject, String[] fieldSequence, Object field) { - return accessField(jsonObject, fieldSequence, new OperationAccess(SET_OPERATION, field)); - } - - - /** - * Retrieve value from jsonMap by field sequence. - * - * @param jsonObject source json object deserialized in map - * @param fieldSequence Sequence to field value. - * Example: person.car.number have to be ["person", "car[2]", "number"] - * @return Field value - */ - public static Object getField(Map jsonObject, List fieldSequence) { - return getField(jsonObject, fieldSequence.toArray(new String[fieldSequence.size()])); - } - - /** - * Delete field value from json Map (field path presented as sequence of string) - * - * @param jsonMap source json object deserialized in map - * @param fieldSequence Sequence to field value. - * Example: person.car.number have to be ["person", "car[2]", "number"] - * @return - */ - public static Object deleteField(Map jsonMap, List fieldSequence) { - return accessField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), new OperationAccess(DELETE_OPERATION, null)); - } - - - @SuppressWarnings("unchecked") - private static Object accessField(Map jsonObject, String[] fieldSequence, OperationAccess op) { - if (fieldSequence == null || fieldSequence.length == 0) { - throw new IllegalArgumentException("Field sequence is empty. Set value to root not possible."); - } - String fieldName = null; - Map jsonObjLocal = jsonObject; - List jsonListLocal = null; - int indexToAccess = -1; - for(int i = 0; i < fieldSequence.length; i++) { - boolean last = i == fieldSequence.length - 1; - fieldName = fieldSequence[i]; - int indOpArray = -1; - for(int ic = 0; ic < fieldName.length(); ) { - if(ic > 0 && (fieldName.charAt(ic) == '[' || fieldName.charAt(ic) == ']') && - fieldName.charAt(ic - 1) == '\\') { - // replace '\[' with '[' - fieldName = fieldName.substring(0, ic - 1) + fieldName.substring(ic); - } else if(fieldName.charAt(ic) == '[') { - indOpArray = ic; - break; - } else { - ic++; - } - } - jsonListLocal = null; // reset - if(indOpArray == -1) { - if(!last) { - Map fieldAccess = (Map) jsonObjLocal.get(fieldName); - if(fieldAccess == null) { - if(op.operation == GET_OPERATION) { - // don't modify during get operation - return null; - } - Map newJsonMap = new TreeMap<>(); - jsonObjLocal.put(fieldName, newJsonMap); - jsonObjLocal = newJsonMap; - } else { - jsonObjLocal = fieldAccess; - } - } - } else { - String arrayFieldName = fieldName.substring(0, indOpArray); - if(arrayFieldName.contains("]")) { - throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); - } - jsonListLocal = (List) jsonObjLocal.get(arrayFieldName); - if (jsonListLocal == null) { - if (op.operation == GET_OPERATION) { - // don't modify during get operation - return null; - } - jsonListLocal = new ArrayList(); - jsonObjLocal.put(arrayFieldName, jsonListLocal); - } - while (indOpArray != -1) { - fieldName = fieldName.substring(indOpArray + 1); - int indClArray = fieldName.indexOf("]"); - if (indClArray == -1) { - throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); - } - if(indClArray == fieldName.length() - 1) { - indOpArray = -1; - } else if(fieldName.charAt(indClArray + 1) == '[') { - indOpArray = indClArray + 1; - } else { - throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); - } - int index = Integer.parseInt(fieldName.substring(0, indClArray)); - if (last && indOpArray == -1) { - indexToAccess = index; - } else { - Object obj = null; - if (index < jsonListLocal.size() && index >= 0) { - obj = jsonListLocal.get(index); - } else if (op.operation == SET_OPERATION && (index == -1 || index == jsonListLocal.size())) { - index = jsonListLocal.size(); - jsonListLocal.add(null); - } else { - throw new IllegalArgumentException( - String.format("Illegal access to array at position %d", index)); - } - - if (obj == null) { - if (op.operation == GET_OPERATION) { - // don't modify during get operation - return null; - } - if (indOpArray == -1) { - obj = new TreeMap<>(); - } else { - obj = new ArrayList(); - } - jsonListLocal.set(index, obj); - } - if(indOpArray != -1) { - jsonListLocal = (List) obj; - } else { - jsonObjLocal = (Map) obj; - jsonListLocal = null; - } - } - } - - } - } - if(jsonListLocal != null) { - return accessListField(op, jsonListLocal, indexToAccess); - } else { - return accessObjField(op, jsonObjLocal, fieldName); - } - } - - private static Object accessObjField(OperationAccess op, Map jsonObjLocal, String fieldName) { - Object prevValue; - if (op.operation == DELETE_OPERATION) { - prevValue = jsonObjLocal.remove(fieldName); - } else if (op.operation == SET_OPERATION) { - prevValue = jsonObjLocal.put(fieldName, op.value); - } else { - prevValue = jsonObjLocal.get(fieldName); - } - return prevValue; - } - - private static Object accessListField(OperationAccess op, List jsonListLocal, int indexToAccess) { - Object prevValue; - int lastIndex = indexToAccess; - if (op.operation == DELETE_OPERATION) { - if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { - prevValue = null; - } else { - prevValue = jsonListLocal.remove(lastIndex); - } - } else if (op.operation == SET_OPERATION) { - if (lastIndex == jsonListLocal.size() || lastIndex == -1) { - prevValue = null; - jsonListLocal.add(op.value); - } else if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { - throw new IllegalArgumentException(String.format("Illegal access to %d position in array with size %d", - lastIndex, jsonListLocal.size())); - } else { - prevValue = jsonListLocal.set(lastIndex, op.value); - } - } else { - if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { - prevValue = null; - } else { - prevValue = jsonListLocal.get(lastIndex); - } - } - return prevValue; - } - - @SuppressWarnings("unchecked") - public static List getIndexObjectByField(Object obj, List field, List res) { - if(obj == null) { - return res; - } - if(field.size() == 0) { - if(res == null) { - res = new ArrayList(); - } - res.add(obj); - return res; - } - if (obj instanceof Map) { - String fieldFirst = field.get(0); - Object value = ((Map) obj).get(fieldFirst); - return getIndexObjectByField(value, field.subList(1, field.size()), res); - } else if(obj instanceof Collection) { - for(Object o : ((Collection)obj)) { - res = getIndexObjectByField(o, field, res); - } - } else { - // we need extract but there no field - LOGGER.warn(String.format("Can't access field %s for object %s", field, obj)); - } - return res; - } - - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java deleted file mode 100644 index 50ed65cba8..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java +++ /dev/null @@ -1,122 +0,0 @@ -package net.osmand.plus.osmedit.utils.util; - -import java.util.List; - -public class OUtils { - - public static boolean isEmpty(String s) { - return s == null || s.trim().length() == 0; - } - - public static boolean validateSqlIdentifier(String id, StringBuilder errorMessage, String field, String action) { - if(isEmpty(id)) { - errorMessage.append(String.format("Field '%s' is not specified which is necessary to %s", field, action)); - return false; - } - if(!isValidJavaIdentifier(id)) { - errorMessage.append(String.format("Value '%s' is not valid for %s to %s", id, field, action)); - return false; - } - return true; - } - - public static long combine(int x1, int x2) { - long l = Integer.toUnsignedLong(x1); - l = (l << 32) | Integer.toUnsignedLong(x2); - return l; - } - - public static int first(long l) { - long s = Integer.MAX_VALUE; - int t = (int) ((l & (s << 32)) >> 32); - if(l < 0) { - t = -t; - } - return t; - } - - public static int second(long l) { - int t = (int) (l & Integer.MAX_VALUE); - if ((l & 0x80000000l) != 0) { - t = -t; - } - return t; - } - - public static boolean isValidJavaIdentifier(String s) { - if (s.isEmpty()) { - return false; - } - if (!Character.isJavaIdentifierStart(s.charAt(0))) { - return false; - } - for (int i = 1; i < s.length(); i++) { - if (!Character.isJavaIdentifierPart(s.charAt(i))) { - return false; - } - } - return true; - } - - public static boolean equals(List s1, List s2) { - if(s1 == null || s1.size() == 0) { - return s2 == null || s2.size() == 0; - } - if(s2 == null || s1.size() != s2.size()) { - return false; - } - for(int i = 0; i < s1.size(); i++) { - Object o1 = s1.get(i); - Object o2 = s2.get(i); - if(o1 == null) { - if(o2 != null) { - return false; - } - } else { - if(!o1.equals(o2)) { - return false; - } - } - } - return true; - } - public static boolean equals(Object s1, Object s2) { - if(s1 == null || s2 == null) { - return s1 == s2; - } - if(s1 instanceof Number && s2 instanceof Number) { - return ((Number) s1).longValue() == ((Number) s2).longValue() && - ((Number) s1).doubleValue() == ((Number) s2).doubleValue(); - } - return s1.equals(s2); - } - - public static boolean equalsStringValue(Object s1, Object s2) { - if(s1 == null || s2 == null) { - return s1 == s2; - } - return s1.toString().equals(s2.toString()); - } - - public static long parseLongSilently(String input, long def) { - if (input != null && input.length() > 0) { - try { - return Long.parseLong(input); - } catch (NumberFormatException e) { - return def; - } - } - return def; - } - - public static int parseIntSilently(String input, int def) { - if (input != null && input.length() > 0) { - try { - return Integer.parseInt(input); - } catch (NumberFormatException e) { - return def; - } - } - return def; - } -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java deleted file mode 100644 index 8a5d4504e3..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.osmand.plus.osmedit.utils.util.exception; - -public class ConnectionException extends TechnicalException { - - private static final long serialVersionUID = 8191498058841215578L; - - public ConnectionException(String message, Throwable cause) { - super(message, cause); - } - - public ConnectionException(String message) { - super(message); - } - -} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java deleted file mode 100644 index eba54a8260..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.osmand.plus.osmedit.utils.util.exception; - -public class FailedVerificationException extends Exception { - - private static final long serialVersionUID = -4936205097177668159L; - - - public FailedVerificationException(Exception e) { - super(e); - } - - - public FailedVerificationException(String msg) { - super(msg); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java deleted file mode 100644 index 70df91a4e8..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.osmand.plus.osmedit.utils.util.exception; - -public class TechnicalException extends RuntimeException { - - private static final long serialVersionUID = 9201898433665734132L; - - public TechnicalException(String message, Throwable cause) { - super(message, cause); - } - - public TechnicalException(String message) { - super(message); - } - -} From 4c4f2c4901730f0d54907b7f6e74860231c98765 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 15 Oct 2020 12:42:52 +0300 Subject: [PATCH 074/123] bug fix --- .../plus/osmedit/OpenstreetmapRemoteUtil.java | 76 +++++++++++++++---- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java index 393dd7d965..b90a22c0b9 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java @@ -18,6 +18,7 @@ import net.osmand.osm.edit.Entity.EntityType; import net.osmand.osm.edit.EntityInfo; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; +import net.osmand.osm.io.Base64; import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.plus.OsmandApplication; @@ -30,10 +31,8 @@ import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; +import java.io.*; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.HashMap; @@ -108,14 +107,16 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { boolean doAuthenticate) { log.info("Sending request " + url); //$NON-NLS-1$ try { - if (doAuthenticate){ - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequest(url,requestMethod,requestBody); - return response.getBody(); - } - else { - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody); + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + if (doAuthenticate) { + if (client.isValidToken()) { + Response response = client.performRequest(url, requestMethod, requestBody); + return response.getBody(); + } else { + return performBasicAuthRequest(url, requestMethod, requestBody, userOperation); + } + } else { + Response response = client.performRequestWithoutAuth(url, requestMethod, requestBody); return response.getBody(); } } catch (NullPointerException e) { @@ -135,7 +136,7 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); - } catch (ExecutionException e) { + } catch (Exception e) { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); @@ -144,6 +145,55 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { return null; } + private String performBasicAuthRequest(String url, String requestMethod, String requestBody, String userOperation) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); + connection.setConnectTimeout(15000); + connection.setRequestMethod(requestMethod); + connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$ + StringBuilder responseBody = new StringBuilder(); + String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$ + connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoInput(true); + if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoOutput(true); + connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$ + OutputStream out = connection.getOutputStream(); + if (requestBody != null) { + BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$ + bwr.write(requestBody); + bwr.flush(); + } + out.close(); + } + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + String msg = userOperation + + " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$ + log.error(msg); + showWarning(msg); + } else { + log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$ + // populate return fields. + responseBody.setLength(0); + InputStream i = connection.getInputStream(); + if (i != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$ + String s; + boolean f = true; + while ((s = in.readLine()) != null) { + if (!f) { + responseBody.append("\n"); //$NON-NLS-1$ + } else { + f = false; + } + responseBody.append(s); + } + } + return responseBody.toString(); + } + return null; + } + public long openChangeSet(String comment) { long id = -1; StringWriter writer = new StringWriter(256); From 98cfb87695722d9079a4f289a6d0f03e5185dd14 Mon Sep 17 00:00:00 2001 From: vshcherb Date: Thu, 15 Oct 2020 14:23:31 +0200 Subject: [PATCH 075/123] Update strings.xml --- OsmAnd/res/values-ru/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 616ee0c58c..4cbbedc326 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3882,7 +3882,7 @@ Остановка записи GPX при принудительном закрытии (через последние приложения). (Из панели уведомлений Android исчезнет значок фонового режима.) сохранен Добавьте хотя бы две точки. - ПОВТОРИТЬ + Повторить • Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам \n \n • Новые настройки вида треков: выбор цвета и толщины линии, указатели направления, метки начала и конца маршрута @@ -3920,6 +3920,6 @@ Войти через OAuth Очистить токен OAuth OpenStreetMap Выход выполнен - »в 997777777:66666776666 + График Файл уже импортирован - \ No newline at end of file + From 29ca9be80458e8151448e6ea1db4cd7ad92bd854 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 13 Oct 2020 19:52:34 +0000 Subject: [PATCH 076/123] Translated using Weblate (Portuguese) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-pt/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index c3292ad2e7..8179f41893 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -3930,4 +3930,9 @@ Logout bem sucedido O ficheiro já é importado em OsmAnd Usar algoritmo de roteamento de 2 fases A* + Para a condução de motos de neve com estradas e pistas dedicadas. + Gráfico + %1$s dados disponíveis apenas nas estradas, precisa calcular uma rota a usar \"Rota entre pontos\" para obtê-la. + Espere pelo recalculo da rota. +\nO gráfico estará disponível após o recalculo. \ No newline at end of file From 3c11a3ac53fa906d1984519fe65f1bfeb8e262b0 Mon Sep 17 00:00:00 2001 From: ffff23 Date: Thu, 15 Oct 2020 15:57:24 +0000 Subject: [PATCH 077/123] Translated using Weblate (Japanese) Currently translated at 98.4% (3454 of 3510 strings) --- OsmAnd/res/values-ja/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index c55ee475f1..86695fc95d 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -927,10 +927,10 @@ POIの更新は利用できません このOsmAnd 無料版はダウンロード数が%1$s個に制限されており、オフラインでのWikipedia記事利用もサポートしていません。 無料版 POIの説明文を表示 - 北米 + 北アメリカ アメリカ合衆国 - 中米 - 南米 + 中央アメリカ + 南アメリカ ヨーロッパ ヨーロッパ - フランス ヨーロッパ - ドイツ @@ -1936,7 +1936,7 @@ POIの更新は利用できません バス 鉄道 現在の経路 - バッテリーレベル + バッテリー残量 マーカーの位置を変更 マップ画面のドラッグでマーカー位置を調整できます From bcea32bb6dffe318d332441fcbf7e4df761da1a5 Mon Sep 17 00:00:00 2001 From: Ldm Public Date: Thu, 15 Oct 2020 15:10:24 +0000 Subject: [PATCH 078/123] Translated using Weblate (French) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-fr/strings.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 3b52399258..04e329659a 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3902,4 +3902,14 @@ Déconnexion réussie Le fichier est déjà importé dans OsmAnd Utiliser un algorithme de routage A* à 2 phases + Le paiement sera débité de votre compte AppGallery dès confirmation de l\'achat. +\n +\nA moins qu\'il ne soit annulé avant sa date de renouvellement, l\'abonnement sera automatiquement débité à chaque échéance (mensuelle / trimestrielle / annuelle). +\n +\nVous pouvez gérer et annuler vos abonnements dans vos paramètres AppGallery. + Seulement %1$s données disponibles sur les routes. Vous devez calculer l\'itinéraire via \"Itinéraire entre 2 points\". + Recalcul de l\'itinéraire en cours. +\nLe graphique sera disponible à l\'issue du calcul. + Pour la conduite en motoneige avec des routes et des pistes dédiées. + Graphique \ No newline at end of file From b1e683e3f113b29741eaa2374cbc5a174fd503d1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 14 Oct 2020 06:48:00 +0000 Subject: [PATCH 079/123] Translated using Weblate (German) Currently translated at 99.7% (3501 of 3510 strings) --- OsmAnd/res/values-de/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index fbf1ccc31f..dec24d8f5d 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -3925,4 +3925,8 @@ Zwei-Phasen-Routenberechnung für die Autonavigation. Native ÖPNV Entwicklung Wechseln zu Java (sicher) Berechnung des ÖPNV-Routings + Abmeldung erfolgreich + Datei wurde bereits in OsmAnd importiert + Anmelden über OAuth + OpenStreetMap OAuth-Token löschen \ No newline at end of file From 6a7b66f4a38cf4007887c0651cee30beb5b5e7b6 Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Thu, 15 Oct 2020 12:03:27 +0000 Subject: [PATCH 080/123] Translated using Weblate (Russian) Currently translated at 99.8% (3504 of 3510 strings) --- OsmAnd/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 616ee0c58c..a628a4e31a 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -1336,7 +1336,7 @@ Данные о тайлах: %1$s Обзорная карта мира Время действия (в минутах) - Самолёт + Воздушное судно Лодка Пеший туризм Мотоцикл From 670b842e5e25fed3e6ed84086bd6d93524a81e46 Mon Sep 17 00:00:00 2001 From: solokot Date: Wed, 14 Oct 2020 11:05:33 +0000 Subject: [PATCH 081/123] Translated using Weblate (Russian) Currently translated at 99.8% (3504 of 3510 strings) --- OsmAnd/res/values-ru/strings.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index a628a4e31a..e8c82c600a 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3242,8 +3242,8 @@ Буфер Logcat Настройки плагинов Язык и вывод - Переместить файлы данных OsmAnd в новое место назначения\? -\n%1$s > %2$s + Переместить файлы данных OsmAnd в новое место назначения\? +\n%1$s → %2$s По умолчанию %1$s • %2$s %1$s ГБ свободно (из %2$s ГБ) @@ -3349,7 +3349,7 @@ Выберите цвет Вы не можете удалить стандартные профили OsmAnd, но вы можете отключить их на предыдущем экране или переместить вниз. Редактировать профили - Режим навигации определяет правила расчета маршрутов. + Режим навигации определяет правила расчёта маршрутов. Внешний вид профиля Значок, цвет и имя Редактировать список профилей @@ -3920,6 +3920,8 @@ Войти через OAuth Очистить токен OAuth OpenStreetMap Выход выполнен - »в 997777777:66666776666 + График Файл уже импортирован + Дождитесь пересчёта маршрута. +\nГрафик будет доступен после пересчёта. \ No newline at end of file From 94f95115be486ec94b22c9bf8af6eda646649ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 14 Oct 2020 17:46:21 +0000 Subject: [PATCH 082/123] Translated using Weblate (Turkish) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-tr/strings.xml | 33 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index d0aa6a6e35..54debf38a3 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -8,7 +8,7 @@ Konum günlüğü hizmetlerini kullanmak için \"Yolculuk kaydı\" eklentisini etkinleştirin (GPX günlüğü, çevrim içi izleme) Uzak hedefler için tahmini rotayı hesapla Lütfen GPS\'yi ayarlardan açık konuma getirin - Log servisi + Günlük kayıt hizmetleri Rota yok Varış Noktasını kaldır Varış noktası %1$s @@ -78,7 +78,7 @@ Uygulamayı güvenli modda çalıştırın (yerel kod yerine daha yavaş Android kullanarak). Güvenli kip Uygulama güvenli modda çalışıyor (\'Ayarlar\'dan kapatın). - OsmAnd arka plan hizmeti hala çalışıyor. Onu da durdur\? + OsmAnd arka plan hizmeti hala çalışıyor. O da durdurulsun mu\? Ses/Video verisi Navigasyonu durdurmak istediğinizden emin misiniz\? Hedefi (ve ara hedefleri) temizlemek istediğinizden emin misiniz\? @@ -837,7 +837,7 @@ Konum sağlayıcı Ekran kapalıyken konumunuzu izler. Arka planda Osmand başlat - Arka plan navigasyon hizmeti açık olması bir konum sağlayıcı gerektirir. + Arka plan navigasyon hizmeti, bir konum sağlayıcının açık olmasını gerektirir. Süzgeci gizle Süzgeci göster Süzgeç @@ -881,7 +881,7 @@ Çevrim içi arama: Ev numarası, sokak, şehir Çevrim dışı arama Toplam uzaklık %1$s, seyahat süresi %2$d s %3$d dak. - Çevrim içi veya çevrim dışı navigasyon servisi. + Çevrim içi veya çevrim dışı navigasyon hizmeti. Bellek kartındaki depolama klasörüne erişilemiyor! {0} - {1} indir ? {0} için çevrim dışı veri zaten var ({1}). ({2}) güncellensin mi\? @@ -1065,7 +1065,7 @@ Tekrar deneyin Eski uyumsuz Wikipedia verileriniz var. Arşivle\? Ekstra Wikipedia verilerini indir (%1$s MB)\? - Konum servisi kapalı. Aç\? + Konum hizmeti kapalı. Açılsın mı\? Ayrıntıları göster Devre dışı Ev kapı numaraları @@ -1457,8 +1457,8 @@ OSM notları (çevrim içi) Haritayı hareket ettirmek için bir izleme topu aygıtı kullanın. İzleme topu kullan - Arka plan servisi tarafından kullanılan uyanma aralığı: - Arka plan servisi tarafından kullanılan konum yöntemi: + Arka plan hizmeti tarafından kullanılan uyanma aralığı: + Arka plan hizmeti tarafından kullanılan konum yöntemi: Düz gidin Ekran yönlendirme Hiçbir adres belirlenmedi @@ -1466,9 +1466,9 @@ Durakta ulaşım aracı ara Harita yönlendirme \'\'{0}\'\' indeks sürümü desteklenmemektedir - OsmAnd çevrim dışı navigasyon deneysel bir özelliktir ve yaklaşık 20 km\'den daha uzun mesafelerde çalışmaz. -\n -\nNavigasyon geçici olarak çevrim içi CloudMade servisine geçti. + OsmAnd çevrim dışı navigasyon deneysel bir özelliktir ve yaklaşık 20 km\'den daha uzun mesafelerde çalışmaz. +\n +\nNavigasyon geçici olarak çevrim içi CloudMade hizmetine geçti. OsmAnd Yükle - {1} {2} üzerinden {0} MB \? Yakınlaştırma {0} indirmek {1} fayans ({2} MB) Önceden yükleme için en fazla yakınlaştırma @@ -1595,7 +1595,7 @@ Baskça Belarusça Boşnakça - Noktalar arasındaki rotayı hesaplamak + Noktalar arasındaki güzergahı hesapla Konumu sürekli ortada tut Ses Çeşitli @@ -1791,7 +1791,7 @@ Svahili dili İbranice İleri - GPX kaydı açıksa, izleme verilerini belirtilen bir web servisine gönder. + GPX kaydı açıksa, izleme verilerini belirtilen bir web hizmetine gönder. Online izleme (GPX gerekli) Online izleme başlat Online izleme durdurun @@ -2282,7 +2282,7 @@ Geçilmiş-olanı göster Geçilmiş-olanı gizle Haritada harita işaretleyicilerine olan uzaklık ve yönün nasıl belirtileceğini seçin: - Harita oryantasyon eşiği + Harita yönlendirme eşiği Harita yönünün \'Hareket yönü\'nden \'Pusula yönü\'ne geçiş hızını aşağıdan seçin. Rota noktaları olarak kaydet Öncesinde nokta ekle @@ -2362,7 +2362,7 @@ Gezin İçerikler Sonuç - Seyehat + Seyahat rehberleri Toplam Tüm başlangıç noktalarını temizle Grup silindi @@ -3883,4 +3883,9 @@ Oturum kapatma başarılı Dosya zaten OsmAnd\'da içe aktarıldı 2 aşamalı A* yönlendirme algoritması kullan + Ayrılmış yollar ve parkurlarla kar arabası sürüşü için. + Grafik + %1$s verileri yalnızca yollarda kullanılabilir, elde etmek için “Noktalar arasındaki güzergah” kullanarak bir rota hesaplamanız gerekir. + Güzergahın yeniden hesaplanmasını bekleyin. +\nGrafik yeniden hesaplandıktan sonra kullanılabilir olacak. \ No newline at end of file From 38dd04dd97235caa1c17f27feaafe25d92419feb Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 13 Oct 2020 20:29:38 +0000 Subject: [PATCH 083/123] Translated using Weblate (Ukrainian) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-uk/strings.xml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 5a20046ed1..f93a7c0156 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -1147,7 +1147,7 @@ %1$s \nТрек %2$s З’єднатись - Розрахувати маршрут між точками + Обчислити маршрут між точками Відображати позицію завжди в центрі Голос Різне @@ -2293,17 +2293,17 @@ OsmAnd (OSM Automated Navigation Directions) — застосунок для мап і навігації з доступом до безкоштовних глобальних високоякісних даних OpenStreetMap (OSM). \n \nНасолоджуйтесь голосовою та візуальною навігацією, переглядом цікавих точок (англ. POI), створенням та керуванням GPX-треками, використовуючи відображення горизонталей та даних про висоту (за допомогою зовнішнього втулка), вибором між режимами автомобіліста, велосипедиста й пішохода, редагуванням OSM та ще багато чим іншим. - GPS навігація -\n • Вибір між автономним режимом (без зборів за роумінг за кордоном) або через Інтернет (швидше) -\n • Покрокові голосові підказки доставить Вас до місця призначення (записані чи синтезовані голоси) -\n • Повторна маршрутизація кожен раз після відхилення від маршруту -\n • Смуги руху, назви вулиць і приблизний час прибуття допоможуть Вам на шляху -\n • Для того, щоб зробити Вашу подорож безпечнішою, режим дня/ночі автоматично перемикається -\n • Відображення обмежень швидкості та попередження про її перевищення -\n • Мапа масштабується відповідно до Вашої швидкості -\n • Шукати місця за адресою, типом (наприклад, паркування, ресторан, готель, заправна станція, музей) чи географічними координатами -\n • Підтримка проміжних точок на Вашому маршруті -\n • Запис свого власного GPX-треку чи вивантаження готового і слідування за ним + GPS навігація +\n • Вибір між автономним режимом (без зборів за роумінг за кордоном) або через Інтернет (швидше) +\n • Покрокові голосові підказки доставить Вас до місця призначення (записані чи синтезовані голоси) +\n • Повторна маршрутизація кожен раз після відхилення від маршруту +\n • Смуги руху, назви вулиць і приблизний час прибуття допоможуть Вам на шляху +\n • Для того, щоб зробити Вашу подорож безпечнішою, режим дня/ночі автоматично перемикається +\n • Показ обмежень швидкості та попередження про її перевищення +\n • Мапа масштабується відповідно до Вашої швидкості +\n • Шукати місця за адресою, типом (наприклад, паркування, ресторан, готель, заправна станція, музей) чи географічними координатами +\n • Підтримка проміжних точок на Вашому маршруті +\n • Запис свого власного GPX-треку чи вивантаження готового і слідування за ним \n Мапа \n• Відображає POI (цікаві точки) навколо вас @@ -3924,4 +3924,9 @@ Розробка Файл уже імпортовано до OsmAnd Використання 2-фазного A* алгоритму маршрутизації + Для їзди на снігоходах із відведеними дорогами та трасами. + Графік + %1$s дані доступні лише для доріг, вам потрібно обчислити маршрут за допомогою «Маршрут між точками», щоб отримати його. + Дочекайтеся переобчислення маршруту. +\nГрафік буде доступний після переобчислення. \ No newline at end of file From 79d301ed666826705d3c3c0a750d669417f9e1ab Mon Sep 17 00:00:00 2001 From: ace shadow Date: Wed, 14 Oct 2020 19:25:07 +0000 Subject: [PATCH 084/123] Translated using Weblate (Slovak) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-sk/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index 971edf9dda..6575698d3a 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3926,4 +3926,11 @@ Prihlásiť pomocou OAuth Vymazať token OpenStreetMap OAuth Odhlásenie úspešné + Pre jazdu na snežnom vozidle po na to určených cestách. + Súbor je už importovaný v OsmAnd + Použiť dvojfázový algoritmus A* na výpočet trasy + Graf + Údaje %1$s sú dostupné len na cestách, pre ich získanie musíte vypočítať trasu pomocou “Trasa medzi bodmi”. + Počkajte na prepočet trasy. +\nGraf bude dostupný po prepočte. \ No newline at end of file From 578c2724779517d21645341ac215b46fdc932ccb Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Wed, 14 Oct 2020 07:15:30 +0000 Subject: [PATCH 085/123] Translated using Weblate (Hebrew) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-iw/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 2c210a5c47..efe9cb3b27 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3933,4 +3933,9 @@ היציאה הצליחה הקובץ כבר ייובא אל OsmAnd להשתמש באלגוריתם חישוב מסלול דו־שלבי A*‎ + לנהיגה ברכבי שלג עם דרכים ומסלולים יעודיים. + הנתונים של %1$s זמינים בדרכים בלבד, עליך לחשב מסלול באמצעות „מסלול בין נקודות” כדי לקבל אותם. + תרשים + נא להמתין לחישוב המסלול מחדש. +\nהתרשים יהיה זמין לאחר החישוב מחדש. \ No newline at end of file From ba11bbd477e0d9efb2ca4008b7790e0a551ab79e Mon Sep 17 00:00:00 2001 From: Ahmad Alfrhood Date: Thu, 15 Oct 2020 06:29:24 +0000 Subject: [PATCH 086/123] Translated using Weblate (Arabic) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-ar/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index 32c555c423..b60654e873 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -3915,4 +3915,9 @@ تسجيل الخروج بنجاح تم استيراد الملف بالفعل في أوسماند استخدام خوارزمية توجيه من مرحلتين A* + %1$s البيانات المتوفرة فقط على الطرق ، تحتاج إلى حساب طريق باستخدام \"الطريق بين النقاط\" للحصول عليها. + في انتظار إعادة حساب الطريق +\nسيتوفر الرسم البياني بعد إعادة الحساب. + للقيادة على الجليد مع طرق ومسارات مخصصة. + رسم بياني \ No newline at end of file From cecddb563507909b4acf7fcdb1c41b63dc1947a9 Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Thu, 15 Oct 2020 17:11:08 +0000 Subject: [PATCH 087/123] Translated using Weblate (Sardinian) Currently translated at 99.6% (3499 of 3510 strings) --- OsmAnd/res/values-sc/strings.xml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index 3e7f0b8fb7..4a3d545b47 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -89,7 +89,7 @@ Preferèntzias de càrculu de s’àndala Imposta sa lestresa de sa boghe de sintetizatzione vocale (TTS). Lestresa de sa boghe - Càrculu lestru de s’àndala fallidu (%s), rinviu a su càlculu lentu. + Càrculu lestru de s’àndala fallidu (%s), rinviu a su càrculu lentu. Istuda su carculu de s’àndala in duas fases pro s’impreu in màchina. Istuta su carculu cumplessu de s’àndala Pidagnu @@ -1540,7 +1540,7 @@ Dislinda s’artària de su veìculu permìtida pro sos caminos. Non faghet rugrare sas fronteras intre sos istados Recàrculu intelligente de s’àndala - Pro biàgios longos, torra a carculare petzi su cantu initziale de s’àndala. + Torra a carculare petzi su cantu initziale de s’àndala. Podet èssere impreadu pro biàgios longos. Disabilitadu Essi Coloratzione a segunda de sa casta (afiliatzione) de àndala @@ -3824,8 +3824,8 @@ Subraiscrie sa rasta Sarva comente una rasta noa Fùrria s\'àndala - Sa rasta intrea at a èssere torrada a calculare impreende su profilu ischertadu. - Petzi su segmentu imbeniente at a èssere torradu a calculare impreende su profilu ischertadu. + Sa rasta intrea at a èssere torrada a carculare impreende su profilu ischertadu. + Petzi su segmentu imbeniente at a èssere torradu a carculare impreende su profilu ischertadu. Ischerta comente connètere sos puntos: cun una lìnia reta o calculende un\'àndala intre issos comente dislindadu inoghe in suta. Rasta intrea Segmentu imbeniente @@ -3926,4 +3926,9 @@ Essida fata chene problemas Su documentu est giai importadu in OsmAnd Imprea un\'algoritmu de càrculu de s\'àndala A* a duas fases + Pro sa ghia de motoislitas cun caminos e rastas dedicados. + Datos %1$s a disponimentu in sos caminos ebbia. Depes carculare un\'àndala impreende \"Àndala intre puntos\" pro los otènnere. + Gràficu + Iseta su càrculu nou de s\'àndala. +\nSu gràficu at a èssere a disponimentu a pustis de su càrculu. \ No newline at end of file From 25ec49c042484b2d21f53e3e9539675f68453a4e Mon Sep 17 00:00:00 2001 From: ffff23 Date: Thu, 15 Oct 2020 14:26:51 +0000 Subject: [PATCH 088/123] Translated using Weblate (Japanese) Currently translated at 99.4% (3803 of 3825 strings) --- OsmAnd/res/values-ja/phrases.xml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/OsmAnd/res/values-ja/phrases.xml b/OsmAnd/res/values-ja/phrases.xml index 66c5cc2a31..865fa35edc 100644 --- a/OsmAnd/res/values-ja/phrases.xml +++ b/OsmAnd/res/values-ja/phrases.xml @@ -50,8 +50,8 @@ インターネット有り レジャー クラブ - 食堂 - 軽食 + 飲食店 + カフェ・レストラン サービス 工芸 金融機関 @@ -568,9 +568,9 @@ 公園 レクリエーション広場 共有地 - 喫茶店・カフェ + カフェ ビアガーデン - レストラン・食堂 + レストラン ファーストフード バー・立ち呑み屋 フードコート @@ -1012,7 +1012,7 @@ 正面玄関 入り口 出口 - 高速道路の横断歩道 + 横断歩道 営業時間 収集時間 詳細 @@ -3834,4 +3834,5 @@ 行政区 ギブボックス(提供品置場) 簡易給水栓 + 液化天然ガス \ No newline at end of file From 1b26d38c5aa7a090744797907d5760e28fc2c7dc Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 14 Oct 2020 15:10:14 +0000 Subject: [PATCH 089/123] Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-es-rAR/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index 5e5fae0268..b68eaba5e1 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -3929,4 +3929,11 @@ Ingresar a través de OAuth Vaciar llave OAuth de OpenStreetMap Sesión finalizada + Para caminos y senderos exclusivos de motos de nieve. + El archivo ya fue importado en OsmAnd + Usar el algoritmo de enrutamiento A* de 2 fases + Gráfico + %1$s datos disponibles sólo en los caminos, necesitas calcular una ruta usando «Ruta entre puntos» para obtenerla. + Espera el recálculo de la ruta. +\nEl gráfico estará disponible después del recálculo. \ No newline at end of file From 445740bd4b70e3ca7df907b771a3605b3a6f2904 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 14 Oct 2020 15:11:45 +0000 Subject: [PATCH 090/123] Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-es-rAR/phrases.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-es-rAR/phrases.xml b/OsmAnd/res/values-es-rAR/phrases.xml index 88c35ef6f9..4a15cc5cdd 100644 --- a/OsmAnd/res/values-es-rAR/phrases.xml +++ b/OsmAnd/res/values-es-rAR/phrases.xml @@ -3568,8 +3568,8 @@ Radioterapia Advertencia de peligro Categoría de dificultad - н/к (sin categoría) - н/к* (sin categoría, posible peligro) + s/c (sin categoría) + s/c* (sin categoría, posible peligro) 1A 1A* 1B From 3e0a3ff23e69961b89641b9dce9ddf9cb878dbfc Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Wed, 14 Oct 2020 14:15:45 +0000 Subject: [PATCH 091/123] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-pt-rBR/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index 5b99d1ed99..1ea7427da0 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -3923,4 +3923,9 @@ Saída bem sucedida O arquivo já foi importado para OsmAnd Use o algoritmo de roteamento 2-phase A * + Para dirigir em motos de neve com estradas e trilhas exclusivas. + Gráfico + Dados de %1$s disponíveis apenas nas estradas, você precisa calcular uma rota usando “Rota entre pontos” para obtê-la. + Aguarde o recálculo da rota. +\nO gráfico estará disponível após o recálculo. \ No newline at end of file From 91aca937c93dd6503ac0cb032f9c67f1994d8122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 14 Oct 2020 18:32:29 +0000 Subject: [PATCH 092/123] Translated using Weblate (Turkish) Currently translated at 81.5% (3119 of 3825 strings) --- OsmAnd/res/values-tr/phrases.xml | 91 ++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index 0353ad6b71..a4b87bee1b 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -269,7 +269,7 @@ Otoyol kavşağı Birleşim Dinlenme alanı - Su kaynağı + Su kuyusu Yangın musluğu Su işleri Tersane @@ -841,7 +841,7 @@ Düden Şelale Irmak - Akış + Dere Nehrin akıntılı yeri Değerli taş Pelerin @@ -1068,7 +1068,7 @@ Paket servisi Kokteyller Mikro bira imalathanesi - Servis + Hizmet Kabul edilen atık Şömine Mevsimlik @@ -1184,7 +1184,7 @@ Tırmanma kayalığı Evet Tarihi tank - Kar aracı erişimi + Kar arabası erişimi Otobüs erişimi Karavan erişimi Motokaravan erişimi @@ -3047,4 +3047,87 @@ Danışma (çocuk rehberliği): evet Danışma (doğum öncesi): evet Danışma (doğum öncesi): hayır + Uzay üssü + Doğaya salma: hayır + Doğaya salma: evet + Sahiplenme: hayır + Sahiplenme: evet + Sahibi + Çocuk kampı + Fotoğraf stüdyosu + Beslenme takviyeleri + Hayvan besleme yeri + Destek: kule + Destek: çatı + Destek: askıda + Destek: tavan + Destek: reklam panosu + Destek: zemin + Destek: kaide + Destek: ağaç + Destek: duvar + Destek: direk + Tarih ekranı: hayır + Tarih ekranı + Pompa istasyonu + Çıkış: biyogaz + Biyogazın çıkış gücü + Çıkış: vakum + Çıkış: basınçlı hava + Basınçlı havanın çıkış gücü + Çıkış: soğuk su + Çıkış: sıcak hava + Çıkış: buhar + Çıkış: sıcak su + Sıcak suyun çıkış gücü + Çıkış (elektrik): hayır + Çıkış: elektrik + Çıkış gücü + Gerilim + Sera bahçeciliği + Yer çekimi + Meteorolojik + Kamu kullanımı için ölçekler + Konuk yönergeleri: hayır + Konuk yönergeleri: evet + Uçuşa yasak zaman (serbest uçuş) + Serbest uçuş alanı yönlendirmesi: KB + Serbest uçuş alanı yönlendirmesi: B + Serbest uçuş alanı yönlendirmesi: GB + Serbest uçuş alanı yönlendirmesi: G + Serbest uçuş alanı yönlendirmesi: GD + Serbest uçuş alanı yönlendirmesi: D + Serbest uçuş alanı yönlendirmesi: KD + Serbest uçuş alanı yönlendirmesi: K + Birden çok aile + Aile + Topluluk + Şişelenmiş su + Su deposu + Su tankeri + Sondaj + Pompa + Akan su + Boru hattı + Su kuyusu + Su arıtma tabletleri + Ters osmoz + Klor + Görünürlük: alan + Tartan + Sosyal hizmetler + Sosyal güvenlik + Elektronik tamir: alet + Güneş saati + Dijital ekran + Analog ekran + Ekran: hayır + Ekran: evet + Evet + Su deposu + Dere + Kuru varil + Sütun + Gölet + Gölet \ No newline at end of file From 97dc5a713b82e0a2f0e0138d078c6493ef3191a0 Mon Sep 17 00:00:00 2001 From: Verdulo Date: Wed, 14 Oct 2020 19:46:35 +0000 Subject: [PATCH 093/123] Translated using Weblate (Esperanto) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-eo/strings.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index 727be72322..ece67180a8 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -73,7 +73,7 @@ Volapuko OsmAnd Mapoj kaj Navigado Inversa ordigo - Anstataŭigi komencpunkton per finpunkto + Anstataŭigi komencpunkton per celo Emblemoj de interesejoj Elemento forigita elementoj forigitaj @@ -742,7 +742,7 @@ Vidiga koloro tagoj Konekti - Prikalkuli kurson inter punktojn + Kalkuli kurson inter punktoj Ĉiam montri centrigitan pozicion Loko adresoj tutmondaj @@ -3357,7 +3357,7 @@ Eraro dum enporti %1$s: %2$s %1$s enportita. Blanka - Anstataŭigi %1$s per %2$s + Anstataŭigi: %1$s ⇄ %2$s Komencpunkto Anstataŭigi komencpunkton per celo Simuli vian pozicion uzante registritan GPX‑kurson. @@ -3925,4 +3925,11 @@ Ensaluti per OAuth Forigi ĵetonon OpenStreetMap OAuth Sukcese elsalutinta + Por veturi per motorsledo sur dediĉitaj vojoj. + Dosiero jam estas enportita al OsmAnd + Uzi 2-fazan A* algoritmon de navigo + Diagramo + Datumoj de %1$s estas disponeblaj nur por vojoj, vi devas kalkuli la kurson uzante “kalkuli kurson inter punktoj” por akiri ĝin. + Atendado ĝis la kurso estos rekalkulita. +\nDiagramo estos videbla post rekalkulado. \ No newline at end of file From 2e334ca1bc484a3d5033fef4745937723b7504ac Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 14 Oct 2020 09:00:56 +0000 Subject: [PATCH 094/123] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3510 of 3510 strings) --- OsmAnd/res/values-zh-rTW/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OsmAnd/res/values-zh-rTW/strings.xml b/OsmAnd/res/values-zh-rTW/strings.xml index a5eb5c24fb..d84b6fcd82 100644 --- a/OsmAnd/res/values-zh-rTW/strings.xml +++ b/OsmAnd/res/values-zh-rTW/strings.xml @@ -3921,4 +3921,11 @@ 透過 OAuth 登入 清除 OpenStreetMap OAuth 權杖 成功登出 + 適用於有專用道路與軌道的雪地摩托車駕駛。 + 檔案已在 OsmAnd 匯入 + 使用 2 相的 A* 路線演算法 + 圖表 + %1$s 資料僅供道路使用,您需要使用「兩點間的路線」來計算路線。 + 等待路線重新計算。 +\n重新計算後即可使用圖表。 \ No newline at end of file From 5d346ce867a6fc10b3c617248f4641ad52b9bb57 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 14 Oct 2020 09:02:32 +0000 Subject: [PATCH 095/123] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-zh-rTW/phrases.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OsmAnd/res/values-zh-rTW/phrases.xml b/OsmAnd/res/values-zh-rTW/phrases.xml index e36aa4414d..add05fe4a7 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -3561,20 +3561,20 @@ 危險 難度分類 放射治療 - н/к - н/к* - - 1А* - - 1Б* - - 2А* - - 2Б* - - 3А* - - 3Б* + n/c + n/c* + 1A + 1A* + 1B + 1B* + 2A + 2A* + 2B + 2B* + 3A + 3A* + 3B + 3B* 燃燒塔 已刪除的物件 攀岩 From 2bd53ec1879c7d7844791f4acd4f450f33d3bb4d Mon Sep 17 00:00:00 2001 From: jan madsen Date: Tue, 13 Oct 2020 18:24:34 +0000 Subject: [PATCH 096/123] Translated using Weblate (Danish) Currently translated at 98.5% (267 of 271 strings) Translation: OsmAnd/Telegram Translate-URL: https://hosted.weblate.org/projects/osmand/telegram/da/ --- OsmAnd-telegram/res/values-da/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) 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 From 27f8af4a72370badab70e93574a54f42dea6c87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 14 Oct 2020 17:41:09 +0000 Subject: [PATCH 097/123] Translated using Weblate (Turkish) Currently translated at 100.0% (271 of 271 strings) Translation: OsmAnd/Telegram Translate-URL: https://hosted.weblate.org/projects/osmand/telegram/tr/ --- OsmAnd-telegram/res/values-tr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd-telegram/res/values-tr/strings.xml b/OsmAnd-telegram/res/values-tr/strings.xml index 87ba56717d..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 From ad6eade116757e0e29eb693fc6d15647333f2d80 Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Thu, 15 Oct 2020 12:14:30 +0000 Subject: [PATCH 098/123] Translated using Weblate (Russian) Currently translated at 99.6% (3811 of 3825 strings) --- OsmAnd/res/values-ru/phrases.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ru/phrases.xml b/OsmAnd/res/values-ru/phrases.xml index d87b1f7c05..7b0db11a82 100644 --- a/OsmAnd/res/values-ru/phrases.xml +++ b/OsmAnd/res/values-ru/phrases.xml @@ -1737,7 +1737,7 @@ Тип приюта: для кошек Тип приюта: для собак и кошек Тип приюта: для лошадей - Исторический самолёт + Историческое воздушное судно Мёд С лифтом Без лифта From b4501a1ea86a899630ef0f57127d00a77a5aa9cd Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Thu, 15 Oct 2020 22:16:01 +0300 Subject: [PATCH 099/123] Broken text fields in Create POI screen --- OsmAnd/res/layout/poi_tag_list_item.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/layout/poi_tag_list_item.xml b/OsmAnd/res/layout/poi_tag_list_item.xml index c057b3f0f1..dc723b5720 100644 --- a/OsmAnd/res/layout/poi_tag_list_item.xml +++ b/OsmAnd/res/layout/poi_tag_list_item.xml @@ -17,6 +17,7 @@ android:layout_marginEnd="@dimen/content_padding"> Date: Fri, 16 Oct 2020 00:13:30 +0300 Subject: [PATCH 100/123] Add multimedia notes to import/export --- .../AudioVideoNoteMenuController.java | 10 ++++---- .../audionotes/AudioVideoNotesPlugin.java | 21 ++++++++++++---- .../settings/backend/ExportSettingsType.java | 2 +- .../backend/backup/FileSettingsItem.java | 3 ++- .../backend/backup/SettingsHelper.java | 22 +++++++++++++++++ .../fragments/DuplicatesSettingsAdapter.java | 7 ++++++ .../ExportImportSettingsAdapter.java | 19 ++++++++++++--- .../fragments/ExportProfileBottomSheet.java | 1 - .../fragments/ImportDuplicatesFragment.java | 24 +++++++++++++------ .../ImportedSettingsItemsAdapter.java | 4 ++++ 10 files changed, 89 insertions(+), 24 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java index f2f31e3a26..a0594ffc66 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNoteMenuController.java @@ -96,13 +96,11 @@ public class AudioVideoNoteMenuController extends MenuController { @Override public Drawable getRightIcon() { - if (mRecording.isPhoto()) { - return getIcon(R.drawable.ic_action_photo_dark, R.color.audio_video_icon_color); - } else if (mRecording.isAudio()) { - return getIcon(R.drawable.ic_action_micro_dark, R.color.audio_video_icon_color); - } else { - return getIcon(R.drawable.ic_action_video_dark, R.color.audio_video_icon_color); + int iconId = AudioVideoNotesPlugin.getIconIdForRecordingFile(mRecording.getFile()); + if (iconId == -1) { + iconId = R.drawable.ic_action_photo_dark; } + return getIcon(iconId, R.color.audio_video_icon_color); } @NonNull diff --git a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java index 0a99d15b03..f59b5869ea 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/AudioVideoNotesPlugin.java @@ -47,14 +47,11 @@ import net.osmand.PlatformUtil; import net.osmand.data.DataTileManager; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuAdapter.ItemClickListener; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.settings.backend.CommonPreference; -import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; @@ -66,12 +63,15 @@ import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.myplaces.FavoritesActivity; import net.osmand.plus.quickaction.QuickActionType; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.CommonPreference; +import net.osmand.plus.settings.backend.OsmandPreference; import net.osmand.plus.settings.fragments.BaseSettingsFragment; -import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; -import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState; +import net.osmand.plus.views.layers.MapInfoLayer; import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget; +import net.osmand.plus.views.mapwidgets.widgetstates.WidgetState; import net.osmand.util.Algorithms; import net.osmand.util.GeoPointParserUtil.GeoParsedPoint; import net.osmand.util.MapUtils; @@ -517,7 +517,18 @@ public class AudioVideoNotesPlugin extends OsmandPlugin { } return additional.toString(); } + } + public static int getIconIdForRecordingFile(@NonNull File file) { + String fileName = file.getName(); + if (fileName.endsWith(IMG_EXTENSION)) { + return R.drawable.ic_action_photo_dark; + } else if (fileName.endsWith(MPEG4_EXTENSION)) { + return R.drawable.ic_action_video_dark; + } else if (fileName.endsWith(THREEGP_EXTENSION)) { + return R.drawable.ic_action_micro_dark; + } + return -1; } // private static void initializeRemoteControlRegistrationMethods() { diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java index 004887d8ee..79e35ffdf8 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java @@ -11,6 +11,6 @@ public enum ExportSettingsType { MARKERS, FAVORITES, TRACKS, - AUDIO_VIDEO_NOTES, + MULTIMEDIA_NOTES, OSM_CHANGES } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java index 7e64e7312a..c8d2838ae9 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java @@ -30,7 +30,8 @@ public class FileSettingsItem extends StreamSettingsItem { TILES_MAP("tiles_map", IndexConstants.TILES_INDEX_DIR), GPX("gpx", IndexConstants.GPX_INDEX_DIR), VOICE("voice", IndexConstants.VOICE_INDEX_DIR), - TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR); + TRAVEL("travel", IndexConstants.WIKIVOYAGE_INDEX_DIR), + MULTIMEDIA_NOTES("multimedia_notes", IndexConstants.AV_INDEX_DIR); private String subtypeName; private String subtypeFolder; diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java index 78d07e893c..9b6df2277d 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -14,7 +14,10 @@ import net.osmand.map.ITileSource; import net.osmand.map.TileSourceManager; import net.osmand.map.TileSourceManager.TileSourceTemplate; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; import net.osmand.plus.SQLiteTileSource; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin.Recording; import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.quickaction.QuickAction; @@ -473,6 +476,19 @@ public class SettingsHelper { if (!impassableRoads.isEmpty()) { dataList.put(ExportSettingsType.AVOID_ROADS, new ArrayList<>(impassableRoads.values())); } + AudioVideoNotesPlugin plugin = OsmandPlugin.getPlugin(AudioVideoNotesPlugin.class); + if (plugin != null) { + List files = new ArrayList<>(); + for (Recording rec : plugin.getAllRecordings()) { + File file = rec.getFile(); + if (file != null && file.exists()) { + files.add(file); + } + } + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.MULTIMEDIA_NOTES, files); + } + } return dataList; } @@ -522,6 +538,7 @@ public class SettingsHelper { List tileSourceTemplates = new ArrayList<>(); List routingFilesList = new ArrayList<>(); List renderFilesList = new ArrayList<>(); + List multimediaFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); for (SettingsItem item : settingsItems) { switch (item.getType()) { @@ -534,6 +551,8 @@ public class SettingsHelper { renderFilesList.add(fileItem.getFile()); } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.ROUTING_CONFIG) { routingFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.MULTIMEDIA_NOTES) { + multimediaFilesList.add(fileItem.getFile()); } break; case QUICK_ACTIONS: @@ -594,6 +613,9 @@ public class SettingsHelper { if (!avoidRoads.isEmpty()) { settingsToOperate.put(ExportSettingsType.AVOID_ROADS, avoidRoads); } + if (!multimediaFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.MULTIMEDIA_NOTES, multimediaFilesList); + } return settingsToOperate; } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java index dc78f5755c..c6ce3232b8 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java @@ -13,6 +13,7 @@ import net.osmand.AndroidUtils; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.map.ITileSource; +import net.osmand.plus.audionotes.AudioVideoNotesPlugin; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; import net.osmand.plus.OsmandApplication; @@ -131,6 +132,12 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter tileSources = new ArrayList<>(); List renderFilesList = new ArrayList<>(); List routingFilesList = new ArrayList<>(); + List multimediaFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); for (Object object : duplicatesList) { @@ -184,10 +188,12 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View tileSources.add((ITileSource) object); } else if (object instanceof File) { File file = (File) object; - if (file.getAbsolutePath().contains("files/rendering")) { + if (file.getAbsolutePath().contains(RENDERERS_DIR)) { renderFilesList.add(file); - } else if (file.getAbsolutePath().contains("files/routing")) { + } else if (file.getAbsolutePath().contains(ROUTING_PROFILES_DIR)) { routingFilesList.add(file); + } else if (file.getAbsolutePath().contains(AV_INDEX_DIR)) { + multimediaFilesList.add(file); } } else if (object instanceof AvoidRoadInfo) { avoidRoads.add((AvoidRoadInfo) object); @@ -217,6 +223,10 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View duplicates.add(getString(R.string.shared_string_rendering_style)); duplicates.addAll(renderFilesList); } + if (!multimediaFilesList.isEmpty()) { + duplicates.add(getString(R.string.audionotes_plugin_name)); + duplicates.addAll(multimediaFilesList); + } if (!avoidRoads.isEmpty()) { duplicates.add(getString(R.string.avoid_road)); duplicates.addAll(avoidRoads); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java index e663e6f189..ef98a624dd 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java @@ -106,6 +106,10 @@ public class ImportedSettingsItemsAdapter extends holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_alert, activeColorRes)); holder.title.setText(R.string.avoid_road); break; + case MULTIMEDIA_NOTES: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_photo_dark, activeColorRes)); + holder.title.setText(R.string.audionotes_plugin_name); + break; } } From fe83d70d661868d26eafc0a64269d844590c4a3c Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Fri, 16 Oct 2020 00:48:32 +0300 Subject: [PATCH 101/123] Add tracks to import/export --- .../backend/backup/SettingsHelper.java | 22 +++++++++++++++++++ .../fragments/DuplicatesSettingsAdapter.java | 4 ++++ .../ExportImportSettingsAdapter.java | 9 ++++++++ .../fragments/ImportDuplicatesFragment.java | 8 +++++++ .../ImportedSettingsItemsAdapter.java | 4 ++++ 5 files changed, 47 insertions(+) diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java index 9b6df2277d..530147b84f 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsHelper.java @@ -19,6 +19,8 @@ import net.osmand.plus.SQLiteTileSource; import net.osmand.plus.audionotes.AudioVideoNotesPlugin; import net.osmand.plus.audionotes.AudioVideoNotesPlugin.Recording; import net.osmand.plus.helpers.AvoidSpecificRoads.AvoidRoadInfo; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.GPXInfo; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.quickaction.QuickAction; import net.osmand.plus.quickaction.QuickActionRegistry; @@ -489,6 +491,20 @@ public class SettingsHelper { dataList.put(ExportSettingsType.MULTIMEDIA_NOTES, files); } } + File gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR); + List gpxInfoList = GpxUiHelper.getSortedGPXFilesInfo(gpxDir, null, true); + if (!gpxInfoList.isEmpty()) { + List files = new ArrayList<>(); + for (GPXInfo gpxInfo : gpxInfoList) { + File file = new File(gpxInfo.getFileName()); + if (file.exists()) { + files.add(file); + } + } + if (!files.isEmpty()) { + dataList.put(ExportSettingsType.TRACKS, files); + } + } return dataList; } @@ -539,6 +555,7 @@ public class SettingsHelper { List routingFilesList = new ArrayList<>(); List renderFilesList = new ArrayList<>(); List multimediaFilesList = new ArrayList<>(); + List tracksFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); for (SettingsItem item : settingsItems) { switch (item.getType()) { @@ -553,6 +570,8 @@ public class SettingsHelper { routingFilesList.add(fileItem.getFile()); } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.MULTIMEDIA_NOTES) { multimediaFilesList.add(fileItem.getFile()); + } else if (fileItem.getSubtype() == FileSettingsItem.FileSubtype.GPX) { + tracksFilesList.add(fileItem.getFile()); } break; case QUICK_ACTIONS: @@ -616,6 +635,9 @@ public class SettingsHelper { if (!multimediaFilesList.isEmpty()) { settingsToOperate.put(ExportSettingsType.MULTIMEDIA_NOTES, multimediaFilesList); } + if (!tracksFilesList.isEmpty()) { + settingsToOperate.put(ExportSettingsType.TRACKS, tracksFilesList); + } return settingsToOperate; } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java index c6ce3232b8..8160e81e6e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/DuplicatesSettingsAdapter.java @@ -14,6 +14,7 @@ import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.map.ITileSource; import net.osmand.plus.audionotes.AudioVideoNotesPlugin; +import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode.ApplicationModeBean; import net.osmand.plus.OsmandApplication; @@ -132,6 +133,9 @@ public class DuplicatesSettingsAdapter extends RecyclerView.Adapter renderFilesList = new ArrayList<>(); List routingFilesList = new ArrayList<>(); List multimediaFilesList = new ArrayList<>(); + List trackFilesList = new ArrayList<>(); List avoidRoads = new ArrayList<>(); for (Object object : duplicatesList) { @@ -194,6 +196,8 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View routingFilesList.add(file); } else if (file.getAbsolutePath().contains(AV_INDEX_DIR)) { multimediaFilesList.add(file); + } else if (file.getAbsolutePath().contains(GPX_INDEX_DIR)) { + trackFilesList.add(file); } } else if (object instanceof AvoidRoadInfo) { avoidRoads.add((AvoidRoadInfo) object); @@ -227,6 +231,10 @@ public class ImportDuplicatesFragment extends BaseOsmAndFragment implements View duplicates.add(getString(R.string.audionotes_plugin_name)); duplicates.addAll(multimediaFilesList); } + if (!trackFilesList.isEmpty()) { + duplicates.add(getString(R.string.shared_string_tracks)); + duplicates.addAll(trackFilesList); + } if (!avoidRoads.isEmpty()) { duplicates.add(getString(R.string.avoid_road)); duplicates.addAll(avoidRoads); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java index ef98a624dd..57198c2f04 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ImportedSettingsItemsAdapter.java @@ -110,6 +110,10 @@ public class ImportedSettingsItemsAdapter extends holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_photo_dark, activeColorRes)); holder.title.setText(R.string.audionotes_plugin_name); break; + case TRACKS: + holder.icon.setImageDrawable(uiUtils.getIcon(R.drawable.ic_action_route_distance, activeColorRes)); + holder.title.setText(R.string.shared_string_tracks); + break; } } From a7af2a3cf0586eb067c29a3758e8576dd46154e1 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Fri, 16 Oct 2020 01:16:42 +0300 Subject: [PATCH 102/123] Fix subfolders for files import --- .../plus/settings/backend/backup/FileSettingsItem.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java index c8d2838ae9..357c0aadbf 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/FileSettingsItem.java @@ -115,7 +115,14 @@ public class FileSettingsItem extends StreamSettingsItem { } else if (subtype == FileSubtype.UNKNOWN || subtype == null) { throw new IllegalArgumentException("Unknown file subtype: " + getFileName()); } else { - this.file = new File(app.getAppPath(subtype.subtypeFolder), name); + String subtypeFolder = subtype.subtypeFolder; + int nameIndex = fileName.indexOf(name); + int folderIndex = fileName.indexOf(subtype.subtypeFolder); + if (nameIndex != -1 && folderIndex != -1) { + String subfolderPath = fileName.substring(folderIndex + subtype.subtypeFolder.length(), nameIndex); + subtypeFolder = subtypeFolder + subfolderPath; + } + this.file = new File(app.getAppPath(subtypeFolder), name); } } From 5e2678f4d7a0eb2b0e01f53ffe72b2be0834833b Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Fri, 16 Oct 2020 11:41:20 +0300 Subject: [PATCH 103/123] Update poi_tag_list_item.xml Text field jumps and loose underline --- OsmAnd/res/layout/poi_tag_list_item.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/OsmAnd/res/layout/poi_tag_list_item.xml b/OsmAnd/res/layout/poi_tag_list_item.xml index dc723b5720..78022f917d 100644 --- a/OsmAnd/res/layout/poi_tag_list_item.xml +++ b/OsmAnd/res/layout/poi_tag_list_item.xml @@ -21,10 +21,7 @@ android:id="@+id/tagEditText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/wpt_list_item_height" android:hint="@string/hint_tag" - android:gravity="bottom" - android:paddingBottom="@dimen/text_margin_small" tools:text="Tag text"/> @@ -39,9 +36,6 @@ android:id="@+id/valueEditText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/wpt_list_item_height" - android:gravity="bottom" - android:paddingBottom="@dimen/text_margin_small" android:hint="@string/hint_value" tools:text="Value text"/> From 082007f810fdbcc34baca7fb7855827023d18f95 Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Fri, 16 Oct 2020 11:57:50 +0300 Subject: [PATCH 104/123] return height --- OsmAnd/res/layout/poi_tag_list_item.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/layout/poi_tag_list_item.xml b/OsmAnd/res/layout/poi_tag_list_item.xml index 78022f917d..e8a9c8dbb5 100644 --- a/OsmAnd/res/layout/poi_tag_list_item.xml +++ b/OsmAnd/res/layout/poi_tag_list_item.xml @@ -21,6 +21,7 @@ android:id="@+id/tagEditText" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="@dimen/wpt_list_item_height" android:hint="@string/hint_tag" tools:text="Tag text"/> @@ -36,6 +37,7 @@ android:id="@+id/valueEditText" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="@dimen/wpt_list_item_height" android:hint="@string/hint_value" tools:text="Value text"/> From 97849a86ad3ee076cf36147ceb1300b9a8636f3d Mon Sep 17 00:00:00 2001 From: xmd5a Date: Fri, 16 Oct 2020 13:10:04 +0300 Subject: [PATCH 105/123] Add phrase --- OsmAnd/res/values/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 87c5c7b4d6..8f17dc5a40 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4259,5 +4259,6 @@ LNG + GPX point From 71401a2b25e852cc60232b1c8abb5aee0e9f8153 Mon Sep 17 00:00:00 2001 From: xmd5a Date: Fri, 16 Oct 2020 13:15:05 +0300 Subject: [PATCH 106/123] Add phrases --- OsmAnd/res/values/phrases.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 8f17dc5a40..0f7636f9cb 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4261,4 +4261,8 @@ GPX point + Rooftop + Sheds + Layby + From 146e97f3dc1f96596aff4f1f0c20bffc67ce8961 Mon Sep 17 00:00:00 2001 From: xmd5a Date: Fri, 16 Oct 2020 21:23:36 +0300 Subject: [PATCH 107/123] Fix phrase (fix https://github.com/osmandapp/OsmAnd/issues/9992) --- OsmAnd/res/values/phrases.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 0f7636f9cb..04b193b923 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4206,7 +4206,7 @@ Yes No - Signal to find the pole + Internet access: customers Only when walking is allowed Contrasted Primitive From 28fdd139fbfed2725f71808658c1697768eef9cd Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Sat, 17 Oct 2020 15:03:32 +0200 Subject: [PATCH 108/123] Fix empty progress --- OsmAnd-java/src/main/java/net/osmand/IProgress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() {} From 07c297a95a6d5fb024a1754b5efa869172b16656 Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Sun, 18 Oct 2020 02:28:44 +0200 Subject: [PATCH 109/123] Fix parse complex properties --- .../java/net/osmand/render/RenderingRule.java | 9 +++-- .../osmand/render/RenderingRuleProperty.java | 36 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) 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..88a7fc83dc 100644 --- a/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java +++ b/OsmAnd-java/src/main/java/net/osmand/render/RenderingRule.java @@ -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) { + } else { + float floatVal = property.parseFloatValue(vl); + if (floatProperties == null && floatVal != 0) { // lazy creates floatProperties = new float[attributes.size()]; + floatProperties[i] = property.parseFloatValue(vl); } - floatProperties[i] = property.parseFloatValue(vl); - intProperties[i] = property.parseIntValue(vl); - } else { intProperties[i] = property.parseIntValue(vl); } i++; 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; } From 0bc9008b9f7ccd18aa1c82ef571652a6c1e4f9fe Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Sun, 18 Oct 2020 02:37:52 +0200 Subject: [PATCH 110/123] Change order --- .../net/osmand/render/RenderingRuleStorageProperties.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)); From 63f06c3d39319a7bb547f2f180db13648a3fa379 Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Sun, 18 Oct 2020 12:52:16 +0300 Subject: [PATCH 111/123] [bug] category names in user defined search converted to lowercase --- OsmAnd-java/src/main/java/net/osmand/osm/MapPoiTypes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From 684f2b95d7a513e8c5efd908e15be11f862099d4 Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Sun, 18 Oct 2020 11:55:23 +0200 Subject: [PATCH 112/123] Fix java/C++ rendering rules property - should match types --- .../main/java/net/osmand/render/RenderingRule.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 88a7fc83dc..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(); @@ -60,11 +60,11 @@ public class RenderingRule { intProperties[i] = storage.getDictionaryValue(vl); } else { float floatVal = property.parseFloatValue(vl); - if (floatProperties == null && floatVal != 0) { - // lazy creates - floatProperties = new float[attributes.size()]; - floatProperties[i] = property.parseFloatValue(vl); - } +// if (floatProperties == null && floatVal != 0) { +// // lazy creates +// floatProperties = new float[attributes.size()]; + floatProperties[i] = floatVal; +// } intProperties[i] = property.parseIntValue(vl); } i++; @@ -94,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; From 2af22671a9fa05887f15bfd52b2d25bc777546ba Mon Sep 17 00:00:00 2001 From: Hardy Date: Sun, 18 Oct 2020 21:14:44 +0200 Subject: [PATCH 113/123] remove accidental commit --- OsmAnd/build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index db678db5c5..3638a103ca 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -230,9 +230,6 @@ android { buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\"" buildConfigField "String", "OSM_OAUTH_CONSUMER_SECRET", "\"lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM\"" signingConfig signingConfigs.development - debuggable false - jniDebuggable false - buildConfigField "boolean", "USE_DEBUG_LIBRARIES", "false" } release { buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\"" From 7e5d9b381bf2c24c9545ed7416aeab925c4e03c2 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 Oct 2020 12:11:10 +0000 Subject: [PATCH 114/123] support incoming content:// URIs pointing to OBF files The whole "handle import" plumbing is already in place to handle both file:// and content:// URIs for OBF files. The `ObfImportTask` gets an `InputStream` from the `ContentResolver` already. `handleContentImport` defers to the `handleFileImport` for which helper to instantiate. Supporting content:// URIs here means this import can happen via `FileProvider` and other `ContentProvider` instances. My use case is offline sharing of OBF files as handled by F-Droid Nearby Swap. Users can select which installed OBF files to share, then nearby users can connect to the device via Bluetooth or local WiFi to get the files. --- OsmAnd/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index d762e0a261..1f24b948b1 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -247,6 +247,7 @@ + @@ -262,6 +263,7 @@ + From e21066eefe6a27b233d6820ae551f501dfa25ade Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Mon, 19 Oct 2020 22:15:00 +0300 Subject: [PATCH 115/123] Favorites duplication with same name --- .../plus/importfiles/FavoritesImportTask.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java b/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java index 9047baeb59..beb3534a46 100644 --- a/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java +++ b/OsmAnd/src/net/osmand/plus/importfiles/FavoritesImportTask.java @@ -34,6 +34,7 @@ class FavoritesImportTask extends BaseImportAsyncTask { protected GPXFile doInBackground(Void... nothing) { List favourites = asFavourites(app, gpxFile.getPoints(), fileName, forceImportFavourites); FavouritesDbHelper favoritesHelper = app.getFavorites(); + checkDuplicateNames(favourites); for (FavouritePoint favourite : favourites) { favoritesHelper.deleteFavourite(favourite, false); favoritesHelper.addFavourite(favourite, false); @@ -43,6 +44,27 @@ class FavoritesImportTask extends BaseImportAsyncTask { return null; } + public void checkDuplicateNames(List favourites) { + for (FavouritePoint fp : favourites) { + int number = 1; + String index; + String name = fp.getName(); + boolean duplicatesFound = false; + for (FavouritePoint fp2 : favourites) { + if (name.equals(fp2.getName()) && fp.getCategory().equals(fp2.getCategory()) && !fp.equals(fp2)) { + if (!duplicatesFound) { + index = " (" + number + ")"; + fp.setName(name + index); + } + duplicatesFound = true; + number++; + index = " (" + number + ")"; + fp2.setName(fp2.getName() + index); + } + } + } + } + @Override protected void onPostExecute(GPXFile result) { hideProgress(); From 3abbffa491dd4556aaa5ac284a2b0868a0ce4a34 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Tue, 20 Oct 2020 14:39:55 +0300 Subject: [PATCH 116/123] Fix profile button visibility for plugin settings --- .../res/layout/profile_preference_toolbar.xml | 7 ++++ .../access/AccessibilitySettingsFragment.java | 35 +++++++++++++++-- .../plus/activities/PluginInfoFragment.java | 5 ++- .../audionotes/MultimediaNotesFragment.java | 39 +++++++++++++++++-- .../MonitoringSettingsFragment.java | 33 ++++++++++++++++ .../fragments/BaseSettingsFragment.java | 6 ++- 6 files changed, 117 insertions(+), 8 deletions(-) diff --git a/OsmAnd/res/layout/profile_preference_toolbar.xml b/OsmAnd/res/layout/profile_preference_toolbar.xml index b76eb61696..6776a7a70c 100644 --- a/OsmAnd/res/layout/profile_preference_toolbar.xml +++ b/OsmAnd/res/layout/profile_preference_toolbar.xml @@ -64,6 +64,13 @@ tools:text="Some description" /> + + \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java b/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java index 781a10bf41..609c43fcff 100644 --- a/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java +++ b/OsmAnd/src/net/osmand/access/AccessibilitySettingsFragment.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.widget.ImageView; @@ -13,21 +15,24 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; -import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.access.AccessibilityMode; import net.osmand.plus.access.RelativeDirectionStyle; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; -import net.osmand.plus.settings.fragments.OnPreferenceChanged; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.fragments.OnPreferenceChanged; import net.osmand.plus.settings.preferences.ListPreferenceEx; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; + public class AccessibilitySettingsFragment extends BaseSettingsFragment implements OnPreferenceChanged, CopyAppModePrefsListener, ResetAppModePrefsListener { private static final String ACCESSIBILITY_OPTIONS = "accessibility_options"; @@ -36,6 +41,8 @@ public class AccessibilitySettingsFragment extends BaseSettingsFragment implemen private AccessibilityStateChangeListener accessibilityListener; + boolean showSwitchProfile = false; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -47,6 +54,28 @@ public class AccessibilitySettingsFragment extends BaseSettingsFragment implemen } } }; + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; } @Override diff --git a/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java index ff396a3ed0..7deb85768c 100644 --- a/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java +++ b/OsmAnd/src/net/osmand/plus/activities/PluginInfoFragment.java @@ -45,6 +45,7 @@ public class PluginInfoFragment extends BaseOsmAndFragment implements PluginStat private static final String TAG = PluginInfoFragment.class.getName(); public static final String EXTRA_PLUGIN_ID = "plugin_id"; + public static final String PLUGIN_INFO = "plugin_info"; private OsmandPlugin plugin; private OsmandApplication app; @@ -135,7 +136,9 @@ public class PluginInfoFragment extends BaseOsmAndFragment implements PluginStat if (activity != null) { SettingsScreenType settingsScreenType = plugin.getSettingsScreenType(); if (settingsScreenType != null) { - BaseSettingsFragment.showInstance(activity, settingsScreenType); + Bundle args = new Bundle(); + args.putBoolean(PLUGIN_INFO, true); + BaseSettingsFragment.showInstance(activity, settingsScreenType, null, args); } } } diff --git a/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java b/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java index 6996f7ee5b..4c1178ef61 100644 --- a/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java +++ b/OsmAnd/src/net/osmand/plus/audionotes/MultimediaNotesFragment.java @@ -13,6 +13,8 @@ import android.os.Build; import android.os.Bundle; import android.os.StatFs; import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; @@ -22,17 +24,18 @@ import androidx.preference.PreferenceViewHolder; import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet; import net.osmand.plus.profiles.SelectCopyAppModeBottomSheet.CopyAppModePrefsListener; -import net.osmand.plus.settings.fragments.BaseSettingsFragment; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet; import net.osmand.plus.settings.bottomsheets.ResetProfilePrefsBottomSheet.ResetAppModePrefsListener; +import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.preferences.ListPreferenceEx; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; import net.osmand.plus.widgets.style.CustomTypefaceSpan; @@ -43,6 +46,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AUDIO_BITRATE_DEFAULT; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_AUTO; import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_CONTINUOUS; @@ -66,6 +70,35 @@ public class MultimediaNotesFragment extends BaseSettingsFragment implements Cop private static final String RESET_TO_DEFAULT = "reset_to_default"; private static final String OPEN_NOTES = "open_notes"; + boolean showSwitchProfile = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; + } + @Override protected void setupPreferences() { AudioVideoNotesPlugin plugin = OsmandPlugin.getPlugin(AudioVideoNotesPlugin.class); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java b/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java index 901348a6d3..ae35fd767c 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/MonitoringSettingsFragment.java @@ -6,10 +6,13 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.view.LayoutInflater; +import android.view.View; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.OsmandPlugin; @@ -32,6 +35,7 @@ import net.osmand.plus.widgets.style.CustomTypefaceSpan; import java.util.HashMap; import java.util.LinkedHashMap; +import static net.osmand.plus.activities.PluginInfoFragment.PLUGIN_INFO; import static net.osmand.plus.settings.backend.OsmandSettings.MONTHLY_DIRECTORY; import static net.osmand.plus.settings.backend.OsmandSettings.REC_DIRECTORY; import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.MINUTES; @@ -46,6 +50,35 @@ public class MonitoringSettingsFragment extends BaseSettingsFragment private static final String OPEN_TRACKS = "open_tracks"; private static final String SAVE_GLOBAL_TRACK_INTERVAL = "save_global_track_interval"; + boolean showSwitchProfile = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) { + showSwitchProfile = args.getBoolean(PLUGIN_INFO, false); + } + } + + @Override + protected void createToolbar(LayoutInflater inflater, View view) { + super.createToolbar(inflater, view); + + View switchProfile = view.findViewById(R.id.profile_button); + if (switchProfile != null) { + AndroidUiHelper.updateVisibility(switchProfile, showSwitchProfile); + } + } + + @Override + public Bundle buildArguments() { + Bundle args = super.buildArguments(); + args.putBoolean(PLUGIN_INFO, showSwitchProfile); + return args; + } + @Override protected void setupPreferences() { setupSaveTrackToGpxPref(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java index de995c2ea0..db12e7d781 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/BaseSettingsFragment.java @@ -899,9 +899,13 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat impl } public static boolean showInstance(FragmentActivity activity, SettingsScreenType screenType, @Nullable ApplicationMode appMode) { + return showInstance(activity, screenType, null, new Bundle()); + } + + public static boolean showInstance(FragmentActivity activity, SettingsScreenType screenType, + @Nullable ApplicationMode appMode, @NonNull Bundle args) { try { Fragment fragment = Fragment.instantiate(activity, screenType.fragmentName); - Bundle args = new Bundle(); if (appMode != null) { args.putString(APP_MODE_KEY, appMode.getStringKey()); } From 9b3eeecf8948a79c043e7ca2f997fea1bab37bfb Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Tue, 20 Oct 2020 14:59:57 +0300 Subject: [PATCH 117/123] Remove outdated plugin settings screens --- OsmAnd/AndroidManifest.xml | 4 - .../access/SettingsAccessibilityActivity.java | 128 ------- .../SettingsAudioVideoActivity.java | 334 ------------------ .../SettingsDevelopmentActivity.java | 212 ----------- .../SettingsMonitoringActivity.java | 325 ----------------- 5 files changed, 1003 deletions(-) delete mode 100644 OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java delete mode 100644 OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java delete mode 100644 OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java delete mode 100644 OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 9569ef9eed..3f3113ff91 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -478,7 +478,6 @@ - - - - diff --git a/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java b/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java deleted file mode 100644 index 719ed05863..0000000000 --- a/OsmAnd/src/net/osmand/access/SettingsAccessibilityActivity.java +++ /dev/null @@ -1,128 +0,0 @@ -package net.osmand.access; - - -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceGroup; -import android.preference.PreferenceScreen; - -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.access.AccessibilityMode; -import net.osmand.plus.access.RelativeDirectionStyle; -import net.osmand.plus.activities.SettingsBaseActivity; - -public class SettingsAccessibilityActivity extends SettingsBaseActivity { - - private ListPreference accessibilityModePreference; - private ListPreference directionStylePreference; - private ListPreference autoannouncePeriodPreference; - - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.shared_string_accessibility); - PreferenceScreen grp = getPreferenceScreen(); - - String[] entries = new String[AccessibilityMode.values().length]; - for (int i = 0; i < entries.length; i++) { - entries[i] = AccessibilityMode.values()[i].toHumanString(getMyApplication()); - } - accessibilityModePreference = createListPreference(settings.ACCESSIBILITY_MODE, entries, AccessibilityMode.values(), - R.string.accessibility_mode, R.string.accessibility_mode_descr); - accessibilityModePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = accessibilityModePreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - addSpeechRateSetting(grp); - - grp.addPreference(accessibilityModePreference); - PreferenceCategory cat = new PreferenceCategory(this); - cat.setKey("accessibility_options"); - cat.setTitle(R.string.accessibility_options); - cat.setEnabled(getMyApplication().accessibilityEnabled()); - grp.addPreference(cat); - - entries = new String[RelativeDirectionStyle.values().length]; - for (int i = 0; i < entries.length; i++) { - entries[i] = RelativeDirectionStyle.values()[i].toHumanString(getMyApplication()); - } - directionStylePreference = createListPreference(settings.DIRECTION_STYLE, entries, RelativeDirectionStyle.values(), - R.string.settings_direction_style, R.string.settings_direction_style_descr); - directionStylePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = directionStylePreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - cat.addPreference(directionStylePreference); - - cat.addPreference(createCheckBoxPreference(settings.ACCESSIBILITY_SMART_AUTOANNOUNCE, R.string.access_smart_autoannounce, - R.string.access_smart_autoannounce_descr)); - - final int[] seconds = new int[] {5, 10, 15, 20, 30, 45, 60, 90}; - final int[] minutes = new int[] {2, 3, 5}; - autoannouncePeriodPreference = createTimeListPreference(settings.ACCESSIBILITY_AUTOANNOUNCE_PERIOD, seconds, minutes, 1000, - R.string.access_autoannounce_period, R.string.access_autoannounce_period_descr); - autoannouncePeriodPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - private final OnPreferenceChangeListener committer = autoannouncePeriodPreference.getOnPreferenceChangeListener(); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (committer != null) - committer.onPreferenceChange(preference, newValue); - updateAllSettings(); - return true; - } - }); - cat.addPreference(autoannouncePeriodPreference); - cat.addPreference(createCheckBoxPreference(settings.DIRECTION_AUDIO_FEEDBACK, R.string.access_direction_audio_feedback, - R.string.access_direction_audio_feedback_descr)); - cat.addPreference(createCheckBoxPreference(settings.DIRECTION_HAPTIC_FEEDBACK, R.string.access_direction_haptic_feedback, - R.string.access_direction_haptic_feedback_descr)); - - } - - - protected void addSpeechRateSetting(PreferenceGroup grp) { - Float[] sprValues = new Float[] {0.5f, 0.75f, 1f, 1.25f, 1.5f, 2f} ; - String[] sprNames = new String[sprValues.length]; - for(int i = 0; i < sprNames.length; i++) { - sprNames[i] = (int)(sprValues[i] * 100) + " %"; - } - grp.addPreference(createListPreference(settings.SPEECH_RATE, sprNames, sprValues, R.string.speech_rate, R.string.speech_rate_descr)); - } - - - - public void updateAllSettings() { - super.updateAllSettings(); - PreferenceCategory accessibilityOptions = ((PreferenceCategory)(getPreferenceScreen().findPreference("accessibility_options"))); - if (accessibilityOptions != null) - accessibilityOptions.setEnabled(getMyApplication().accessibilityEnabled()); - if(accessibilityModePreference != null) { - accessibilityModePreference.setSummary(getString(R.string.accessibility_mode_descr) + " [" + settings.ACCESSIBILITY_MODE.get().toHumanString(getMyApplication()) + "]"); - } - if(directionStylePreference != null) { - directionStylePreference.setSummary(getString(R.string.settings_direction_style_descr) + " [" + settings.DIRECTION_STYLE.get().toHumanString(getMyApplication()) + "]"); - } - if(autoannouncePeriodPreference != null) { - autoannouncePeriodPreference.setSummary(getString(R.string.access_autoannounce_period_descr) + " [" + autoannouncePeriodPreference.getEntry() + "]"); - } - } - -} diff --git a/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java b/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java deleted file mode 100644 index 815b77108c..0000000000 --- a/OsmAnd/src/net/osmand/plus/audionotes/SettingsAudioVideoActivity.java +++ /dev/null @@ -1,334 +0,0 @@ -package net.osmand.plus.audionotes; - -import android.hardware.Camera; -import android.hardware.Camera.Parameters; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.os.Build; -import android.os.Bundle; -import android.os.StatFs; -import android.preference.ListPreference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; - -import net.osmand.AndroidUtils; -import net.osmand.PlatformUtil; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.activities.SettingsBaseActivity; - -import org.apache.commons.logging.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AUDIO_BITRATE_DEFAULT; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_AUTO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_CONTINUOUS; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_EDOF; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_HIPERFOCAL; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_INFINITY; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_CAMERA_FOCUS_MACRO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_AUDIO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_CHOOSE; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_TAKEPICTURE; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.AV_DEFAULT_ACTION_VIDEO; -import static net.osmand.plus.audionotes.AudioVideoNotesPlugin.cameraPictureSizeDefault; - -// camera picture size: -// support camera focus select: -//// - -public class SettingsAudioVideoActivity extends SettingsBaseActivity { - - private static final Log log = PlatformUtil.getLog(AudioVideoNotesPlugin.class); - - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.av_settings); - PreferenceScreen grp = getPreferenceScreen(); - AudioVideoNotesPlugin p = OsmandPlugin.getEnabledPlugin(AudioVideoNotesPlugin.class); - if (p != null) { - String[] entries; - Integer[] intValues; - - entries = new String[]{getString(R.string.av_def_action_choose), getString(R.string.av_def_action_audio), - getString(R.string.av_def_action_video), getString(R.string.av_def_action_picture)}; - intValues = new Integer[]{AV_DEFAULT_ACTION_CHOOSE, AV_DEFAULT_ACTION_AUDIO, AV_DEFAULT_ACTION_VIDEO, - AV_DEFAULT_ACTION_TAKEPICTURE}; - ListPreference defAct = createListPreference(p.AV_DEFAULT_ACTION, entries, intValues, R.string.av_widget_action, - R.string.av_widget_action_descr); - grp.addPreference(defAct); - - PreferenceCategory photo = new PreferenceCategory(this); - photo.setTitle(R.string.shared_string_photo); - grp.addPreference(photo); - - final Camera cam = openCamera(); - if (cam != null) { - // camera type settings - photo.addPreference(createCheckBoxPreference(p.AV_EXTERNAL_PHOTO_CAM, R.string.av_use_external_camera, - R.string.av_use_external_camera_descr)); - - Parameters parameters = cam.getParameters(); - createCameraPictureSizesPref(p, photo, parameters); - createCameraFocusModesPref(p, photo, parameters); - - // play sound on success photo - photo.addPreference(createCheckBoxPreference(p.AV_PHOTO_PLAY_SOUND, R.string.av_photo_play_sound, - R.string.av_photo_play_sound_descr)); - - cam.release(); - } - - // video settings - PreferenceCategory video = new PreferenceCategory(this); - video.setTitle(R.string.shared_string_video); - grp.addPreference(video); - - video.addPreference(createCheckBoxPreference(p.AV_EXTERNAL_RECORDER, R.string.av_use_external_recorder, - R.string.av_use_external_recorder_descr)); - -// entries = new String[] { "3GP", "MP4" }; -// intValues = new Integer[] { VIDEO_OUTPUT_3GP, VIDEO_OUTPUT_MP4 }; -// ListPreference lp = createListPreference(p.AV_VIDEO_FORMAT, entries, intValues, R.string.av_video_format, -// R.string.av_video_format_descr); -// video.addPreference(lp); - - List qNames = new ArrayList<>(); - List qValues = new ArrayList<>(); - if (Build.VERSION.SDK_INT < 11 || CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)) { - qNames.add(getString(R.string.av_video_quality_low)); - qValues.add(CamcorderProfile.QUALITY_LOW); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { - qNames.add("720 x 480 (480p)"); - qValues.add(CamcorderProfile.QUALITY_480P); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { - qNames.add("1280 x 720 (720p)"); - qValues.add(CamcorderProfile.QUALITY_720P); - } - if (Build.VERSION.SDK_INT >= 11 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { - qNames.add("1920 x 1080 (1080p)"); - qValues.add(CamcorderProfile.QUALITY_1080P); - } - if (Build.VERSION.SDK_INT >= 21 && CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { - qNames.add("3840x2160 (2160p)"); - qValues.add(CamcorderProfile.QUALITY_2160P); - } - if (Build.VERSION.SDK_INT < 11 || CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) { - qNames.add(getString(R.string.av_video_quality_high)); - qValues.add(CamcorderProfile.QUALITY_HIGH); - } - - ListPreference lp = createListPreference(p.AV_VIDEO_QUALITY, - qNames.toArray(new String[qNames.size()]), - qValues.toArray(new Integer[qValues.size()]), - R.string.av_video_quality, - R.string.av_video_quality_descr); - video.addPreference(lp); - - // Recorder Split settings - PreferenceCategory recSplit = new PreferenceCategory(this); - recSplit.setTitle(R.string.rec_split); - grp.addPreference(recSplit); - - recSplit.addPreference(createCheckBoxPreference(p.AV_RECORDER_SPLIT, R.string.rec_split_title, - R.string.rec_split_desc)); - - intValues = new Integer[]{1, 2, 3, 4, 5, 7, 10, 15, 20, 25, 30}; - entries = new String[intValues.length]; - int i = 0; - String minStr = getString(R.string.int_min); - for (int v : intValues) { - entries[i++] = String.valueOf(v) + " " + minStr; - } - lp = createListPreference(p.AV_RS_CLIP_LENGTH, entries, intValues, - R.string.rec_split_clip_length, - R.string.rec_split_clip_length_desc); - recSplit.addPreference(lp); - - File dir = getMyApplication().getAppPath("").getParentFile(); - long size = 0; - if (dir.canRead()) { - StatFs fs = new StatFs(dir.getAbsolutePath()); - size = ((long) fs.getBlockSize() * (long) fs.getBlockCount()) / (1 << 30); - } - if (size > 0) { - int value = 1; - ArrayList gbList = new ArrayList<>(); - while (value < size) { - gbList.add(value); - if (value < 5) { - value++; - } else { - value += 5; - } - } - if (value != size) { - gbList.add((int) size); - } - entries = new String[gbList.size()]; - intValues = new Integer[gbList.size()]; - i = 0; - for (int v : gbList) { - intValues[i] = v; - entries[i] = AndroidUtils.formatSize(this, v * (1l << 30)); - i++; - } - - lp = createListPreference(p.AV_RS_STORAGE_SIZE, entries, intValues, - R.string.rec_split_storage_size, - R.string.rec_split_storage_size_desc); - recSplit.addPreference(lp); - } - - // audio settings - PreferenceCategory audio = new PreferenceCategory(this); - audio.setTitle(R.string.shared_string_audio); - grp.addPreference(audio); - - entries = new String[]{"Default", "AAC"}; - intValues = new Integer[]{MediaRecorder.AudioEncoder.DEFAULT, MediaRecorder.AudioEncoder.AAC}; - lp = createListPreference(p.AV_AUDIO_FORMAT, entries, intValues, - R.string.av_audio_format, - R.string.av_audio_format_descr); - audio.addPreference(lp); - - entries = new String[]{"Default", "16 kbps", "32 kbps", "48 kbps", "64 kbps", "96 kbps", "128 kbps"}; - intValues = new Integer[]{AUDIO_BITRATE_DEFAULT, 16 * 1024, 32 * 1024, 48 * 1024, 64 * 1024, 96 * 1024, 128 * 1024}; - lp = createListPreference(p.AV_AUDIO_BITRATE, entries, intValues, - R.string.av_audio_bitrate, - R.string.av_audio_bitrate_descr); - audio.addPreference(lp); - } - } - - private void createCameraPictureSizesPref(AudioVideoNotesPlugin p, PreferenceCategory photo, Parameters parameters) { - String[] entries; - Integer[] intValues; - // Photo picture size - // get supported sizes - List psps = parameters.getSupportedPictureSizes(); - if (psps == null) { - return; - } - // list of megapixels of each resolution - List mpix = new ArrayList(); - // list of index each resolution in list, returned by getSupportedPictureSizes() - List picSizesValues = new ArrayList(); - // fill lists for sort - for (int index = 0; index < psps.size(); index++) { - mpix.add((psps.get(index)).width * (psps.get(index)).height); - picSizesValues.add(index); - } - // sort list for max resolution in begining of list - for (int i = 0; i < mpix.size(); i++) { - for (int j = 0; j < mpix.size() - i - 1; j++) { - if (mpix.get(j) < mpix.get(j + 1)) { - // change elements - int tmp = mpix.get(j + 1); - mpix.set(j + 1, mpix.get(j)); - mpix.set(j, tmp); - - tmp = picSizesValues.get(j + 1); - picSizesValues.set(j + 1, picSizesValues.get(j)); - picSizesValues.set(j, tmp); - } - } - } - // set default photo size to max resolution (set index of element with max resolution in List, returned by getSupportedPictureSizes() ) - cameraPictureSizeDefault = picSizesValues.get(0); - log.debug("onCreate() set cameraPictureSizeDefault=" + cameraPictureSizeDefault); - - List itemsPicSizes = new ArrayList(); - String prefix; - for (int index = 0; index < psps.size(); index++) { - float px = (float) ((psps.get(picSizesValues.get(index))).width * (psps.get(picSizesValues.get(index))).height); - if (px > 102400) // 100 K - { - px = px / 1048576; - prefix = "Mpx"; - } else { - px = px / 1024; - prefix = "Kpx"; - } - - itemsPicSizes.add((psps.get(picSizesValues.get(index))).width + - "x" + - (psps.get(picSizesValues.get(index))).height + - " ( " + - String.format("%.2f", px) + - " " + - prefix + - " )"); - } - log.debug("onCreate() set default size: width=" + psps.get(cameraPictureSizeDefault).width + " height=" - + psps.get(cameraPictureSizeDefault).height + " index in ps=" + cameraPictureSizeDefault); - - entries = itemsPicSizes.toArray(new String[itemsPicSizes.size()]); - intValues = picSizesValues.toArray(new Integer[picSizesValues.size()]); - if (entries.length > 0) { - ListPreference camSizes = createListPreference(p.AV_CAMERA_PICTURE_SIZE, entries, intValues, R.string.av_camera_pic_size, - R.string.av_camera_pic_size_descr); - photo.addPreference(camSizes); - } - } - - private void createCameraFocusModesPref(AudioVideoNotesPlugin p, PreferenceCategory photo, Parameters parameters) { - String[] entries; - Integer[] intValues; - // focus mode settings - // show in menu only suppoted modes - List sfm = parameters.getSupportedFocusModes(); - if (sfm == null) { - return; - } - List items = new ArrayList(); - List itemsValues = new ArrayList(); - // filtering known types for translate and set index - for (int index = 0; index < sfm.size(); index++) { - if (sfm.get(index).equals("auto")) { - items.add(getString(R.string.av_camera_focus_auto)); - itemsValues.add(AV_CAMERA_FOCUS_AUTO); - } else if (sfm.get(index).equals("fixed")) { - items.add(getString(R.string.av_camera_focus_hiperfocal)); - itemsValues.add(AV_CAMERA_FOCUS_HIPERFOCAL); - } else if (sfm.get(index).equals("edof")) { - items.add(getString(R.string.av_camera_focus_edof)); - itemsValues.add(AV_CAMERA_FOCUS_EDOF); - } else if (sfm.get(index).equals("infinity")) { - items.add(getString(R.string.av_camera_focus_infinity)); - itemsValues.add(AV_CAMERA_FOCUS_INFINITY); - } else if (sfm.get(index).equals("macro")) { - items.add(getString(R.string.av_camera_focus_macro)); - itemsValues.add(AV_CAMERA_FOCUS_MACRO); - } else if (sfm.get(index).equals("continuous-picture")) { - items.add(getString(R.string.av_camera_focus_continuous)); - itemsValues.add(AV_CAMERA_FOCUS_CONTINUOUS); - } - } - entries = items.toArray(new String[items.size()]); - intValues = itemsValues.toArray(new Integer[itemsValues.size()]); - if (entries.length > 0) { - ListPreference camFocus = createListPreference(p.AV_CAMERA_FOCUS_TYPE, entries, intValues, R.string.av_camera_focus, - R.string.av_camera_focus_descr); - photo.addPreference(camFocus); - } - } - - protected Camera openCamera() { - try { - return Camera.open(); - } catch (Exception e) { - log.error("Error open camera", e); - return null; - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java deleted file mode 100644 index c500a28254..0000000000 --- a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java +++ /dev/null @@ -1,212 +0,0 @@ -package net.osmand.plus.development; - - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.Bundle; -import android.os.Debug; -import android.os.Debug.MemoryInfo; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; - -import net.osmand.plus.OsmAndLocationSimulation; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.Version; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.render.NativeOsmandLibrary; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.util.SunriseSunset; - -import java.text.SimpleDateFormat; - -//import net.osmand.plus.development.OsmandDevelopmentPlugin; - -public class SettingsDevelopmentActivity extends SettingsBaseActivity { - - @SuppressLint("SimpleDateFormat") - @Override - public void onCreate(Bundle savedInstanceState) { - OsmandApplication app = getMyApplication(); - app.applyTheme(this); - super.onCreate(savedInstanceState); - getToolbar().setTitle(R.string.debugging_and_development); - PreferenceScreen category = getPreferenceScreen(); - Preference pref; - - if (Version.isOpenGlAvailable(app)) { - category.addPreference(createCheckBoxPreference(settings.USE_OPENGL_RENDER, - R.string.use_opengl_render, R.string.use_opengl_render_descr)); - } - - if (!Version.isBlackberry(app)) { - CheckBoxPreference nativeCheckbox = createCheckBoxPreference(settings.SAFE_MODE, R.string.safe_mode, R.string.safe_mode_description); - // disable the checkbox if the library cannot be used - if ((NativeOsmandLibrary.isLoaded() && !NativeOsmandLibrary.isSupported()) || settings.NATIVE_RENDERING_FAILED.get()) { - nativeCheckbox.setEnabled(false); - nativeCheckbox.setChecked(true); - } - category.addPreference(nativeCheckbox); - } - - PreferenceCategory navigation = new PreferenceCategory(this); - navigation.setTitle(R.string.routing_settings); - category.addPreference(navigation); - pref = new Preference(this); - final Preference simulate = pref; - final OsmAndLocationSimulation sim = getMyApplication().getLocationProvider().getLocationSimulation(); - final Runnable updateTitle = new Runnable(){ - - @Override - public void run() { - simulate.setSummary(sim.isRouteAnimating() ? - R.string.simulate_your_location_stop_descr : R.string.simulate_your_location_gpx_descr); - } - }; - pref.setTitle(R.string.simulate_your_location); - updateTitle.run(); - pref.setKey("simulate_your_location"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - updateTitle.run(); - sim.startStopRouteAnimation(SettingsDevelopmentActivity.this, true, updateTitle); - return true; - } - }); - navigation.addPreference(pref); - - PreferenceCategory debug = new PreferenceCategory(this); - debug.setTitle(R.string.debugging_and_development); - category.addPreference(debug); - - CheckBoxPreference dbg = createCheckBoxPreference(settings.DEBUG_RENDERING_INFO, - R.string.trace_rendering, R.string.trace_rendering_descr); - debug.addPreference(dbg); - - - final Preference firstRunPreference = new Preference(this); - firstRunPreference.setTitle(R.string.simulate_initial_startup); - firstRunPreference.setSummary(R.string.simulate_initial_startup_descr); - firstRunPreference.setSelectable(true); - firstRunPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - getMyApplication().getAppInitializer().resetFirstTimeRun(); - OsmandSettings settings = getMyApplication().getSettings(); - settings.FIRST_MAP_IS_DOWNLOADED.set(false); - settings.MAPILLARY_FIRST_DIALOG_SHOWN.set(false); - settings.WEBGL_SUPPORTED.set(true); - settings.WIKI_ARTICLE_SHOW_IMAGES_ASKED.set(false); - - getMyApplication().showToastMessage(R.string.shared_string_ok); - return true; - } - }); - debug.addPreference(firstRunPreference); - - debug.addPreference(createCheckBoxPreference(settings.SHOULD_SHOW_FREE_VERSION_BANNER, - R.string.show_free_version_banner, - R.string.show_free_version_banner_description)); - - pref = new Preference(this); - pref.setTitle(R.string.test_voice_prompts); - pref.setSummary(R.string.play_commands_of_currently_selected_voice); - pref.setKey("test_voice_commands"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsDevelopmentActivity.this, TestVoiceActivity.class)); - return true; - } - }); - category.addPreference(pref); - - pref = new Preference(this); - pref.setTitle(R.string.logcat_buffer); - pref.setSummary(R.string.logcat_buffer_descr); - pref.setKey("logcat_buffer"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startActivity(new Intent(SettingsDevelopmentActivity.this, LogcatActivity.class)); - return true; - } - }); - category.addPreference(pref); - - PreferenceCategory info = new PreferenceCategory(this); - info.setTitle(R.string.info_button); - category.addPreference(info); - - pref = new Preference(this); - pref.setTitle(R.string.global_app_allocated_memory); - - long javaAvailMem = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/ (1024*1024l); - long javaTotal = Runtime.getRuntime().totalMemory() / (1024*1024l); - long dalvikSize = android.os.Debug.getNativeHeapAllocatedSize() / (1024*1024l); - pref.setSummary(getString(R.string.global_app_allocated_memory_descr, javaAvailMem, javaTotal, dalvikSize)); - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - -// ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); -// ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); -// activityManager.getMemoryInfo(memoryInfo); -// long totalSize = memoryInfo.availMem / (1024*1024l); - MemoryInfo mem = new Debug.MemoryInfo(); - Debug.getMemoryInfo(mem); - pref = new Preference(this); - pref.setTitle(R.string.native_app_allocated_memory); - pref.setSummary(getString(R.string.native_app_allocated_memory_descr - , mem.nativePrivateDirty / 1024, mem.dalvikPrivateDirty / 1024 , mem.otherPrivateDirty / 1024 - , mem.nativePss / 1024, mem.dalvikPss / 1024 , mem.otherPss / 1024)); - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - - final Preference agpspref = new Preference(this); - agpspref.setTitle(R.string.agps_info); - if (settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get() != 0L) { - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, prt.format(settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get()))); - } else { - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, "--")); - } - agpspref.setSelectable(true); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - agpspref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - if(getMyApplication().getSettings().isInternetConnectionAvailable(true)) { - getMyApplication().getLocationProvider().redownloadAGPS(); - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - agpspref.setSummary(getString(R.string.agps_data_last_downloaded, prt.format(settings.AGPS_DATA_LAST_TIME_DOWNLOADED.get()))); - } - return true; - } - }); - info.addPreference(agpspref); - - SunriseSunset sunriseSunset = getMyApplication().getDaynightHelper().getSunriseSunset(); - pref = new Preference(this); - pref.setTitle(R.string.day_night_info); - if (sunriseSunset != null && sunriseSunset.getSunrise() != null && sunriseSunset.getSunset() != null) { - SimpleDateFormat prt = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - pref.setSummary(getString(R.string.day_night_info_description, prt.format(sunriseSunset.getSunrise()), - prt.format(sunriseSunset.getSunset()))); - } else { - pref.setSummary(getString(R.string.day_night_info_description, "null", "null")); - } - pref.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //pref.setEnabled(false); - info.addPreference(pref); - } -} diff --git a/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java b/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java deleted file mode 100644 index 7ace6377ad..0000000000 --- a/OsmAnd/src/net/osmand/plus/monitoring/SettingsMonitoringActivity.java +++ /dev/null @@ -1,325 +0,0 @@ -package net.osmand.plus.monitoring; - - -import android.content.BroadcastReceiver; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; -import android.text.SpannableString; -import android.text.style.StyleSpan; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import net.osmand.plus.OsmAndFormatter; -import net.osmand.plus.OsmAndTaskManager.OsmAndTaskRunnable; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.activities.SavingTrackHelper; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.util.Algorithms; - -import java.util.Map; - -import static net.osmand.plus.settings.backend.OsmandSettings.MONTHLY_DIRECTORY; -import static net.osmand.plus.settings.backend.OsmandSettings.REC_DIRECTORY; - -public class SettingsMonitoringActivity extends SettingsBaseActivity { - - public static final String PROFILE_STRING_KEY = "string_key"; - - private CheckBoxPreference routeServiceEnabled; - private BroadcastReceiver broadcastReceiver; - - public static final int[] BG_SECONDS = new int[]{0, 30, 60, 90}; - public static final int[] BG_MINUTES = new int[]{2, 3, 5, 10, 15, 30, 60, 90}; - private static final int[] SECONDS = OsmandMonitoringPlugin.SECONDS; - private static final int[] MINUTES = OsmandMonitoringPlugin.MINUTES; - private static final int[] MAX_INTERVAL_TO_SEND_MINUTES = OsmandMonitoringPlugin.MAX_INTERVAL_TO_SEND_MINUTES; - - public SettingsMonitoringActivity() { - super(true); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - ((OsmandApplication) getApplication()).applyTheme(this); - requestWindowFeature(Window.FEATURE_PROGRESS); - super.onCreate(savedInstanceState); - setProgressVisibility(false); - getToolbar().setTitle(R.string.monitoring_settings); - PreferenceScreen grp = getPreferenceScreen(); - - createLoggingSection(grp); - createLiveSection(grp); - createNotificationSection(grp); - - Intent intent = getIntent(); - if (intent != null && intent.hasExtra(PROFILE_STRING_KEY)) { - String modeName = intent.getStringExtra(PROFILE_STRING_KEY); - selectedAppMode = ApplicationMode.valueOfStringKey(modeName, ApplicationMode.CAR); - } else { - selectAppModeDialog().show(); - } - } - - - private void createLoggingSection(PreferenceScreen grp) { - PreferenceCategory cat = new PreferenceCategory(this); - cat.setTitle(R.string.save_track_to_gpx_globally); - grp.addPreference(cat); - - Preference globalrecord = new Preference(this); - globalrecord.setTitle(R.string.save_track_to_gpx_globally_headline); - globalrecord.setSummary(R.string.save_track_to_gpx_globally_descr); - globalrecord.setSelectable(false); - //setEnabled(false) creates bad readability on some devices - //globalrecord.setEnabled(false); - cat.addPreference(globalrecord); - - if(settings.SAVE_GLOBAL_TRACK_REMEMBER.get()) { - cat.addPreference(createTimeListPreference(settings.SAVE_GLOBAL_TRACK_INTERVAL, SECONDS, - MINUTES, 1000, settings.SAVE_GLOBAL_TRACK_REMEMBER, R.string.save_global_track_interval, R.string.save_global_track_interval_descr)); - } - - Preference pref = new Preference(this); - pref.setTitle(R.string.save_current_track); - pref.setSummary(getMyApplication().getString(R.string.save_current_track_descr) - + " (" + OsmAndFormatter.getFormattedDistance(getMyApplication().getSavingTrackHelper().getDistance(), getMyApplication()) + ")"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - SavingTrackHelper helper = getMyApplication().getSavingTrackHelper(); - if (helper.hasDataToSave()) { - saveCurrentTracks(helper); - } else { - helper.close(); - } - return true; - } - }); - cat.addPreference(pref); - - cat.addPreference(createCheckBoxPreference(settings.SAVE_TRACK_TO_GPX, R.string.save_track_to_gpx, - R.string.save_track_to_gpx_descrp)); - cat.addPreference(createTimeListPreference(settings.SAVE_TRACK_INTERVAL, SECONDS, - MINUTES, 1000, R.string.save_track_interval, R.string.save_track_interval_descr)); - String[] names; - Float[] floatValues; - floatValues = new Float[] {0.f, 2.0f, 5.0f, 10.0f, 20.0f, 30.0f, 50.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - for(int i = 1; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.m); - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_MIN_DISTANCE, names, floatValues, - R.string.save_track_min_distance, R.string.save_track_min_distance_descr)); - floatValues = new Float[] {0.f, 1.0f, 2.0f, 5.0f, 10.0f, 15.0f, 20.0f, 50.0f, 100.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - for(int i = 1; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.m) + " (" + Math.round(floatValues[i]/0.3048f) + " " + getString(R.string.foot) + ")"; - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_PRECISION, names, floatValues, - R.string.save_track_precision, R.string.save_track_precision_descr)); - floatValues = new Float[] {0.f, 0.000001f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - names = new String[floatValues.length]; - names[0] = getString(R.string.shared_string_not_selected); - names[1] = "> 0"; // This option for the GPS chipset motion detection - for(int i = 2; i < floatValues.length; i++) { - names[i] = floatValues[i].intValue() + " " + getString(R.string.km_h); - floatValues[i] = floatValues[i] / 3.6f; - } - cat.addPreference(createListPreference(settings.SAVE_TRACK_MIN_SPEED, names, floatValues, - R.string.save_track_min_speed, R.string.save_track_min_speed_descr)); - cat.addPreference(createCheckBoxPreference(settings.AUTO_SPLIT_RECORDING, R.string.auto_split_recording_title, - R.string.auto_split_recording_descr)); - cat.addPreference(createCheckBoxPreference(settings.DISABLE_RECORDING_ONCE_APP_KILLED, R.string.disable_recording_once_app_killed, - R.string.disable_recording_once_app_killed_descrp)); - cat.addPreference(createCheckBoxPreference(settings.SAVE_HEADING_TO_GPX, R.string.save_heading, - R.string.save_heading_descr)); - - Integer[] intValues = new Integer[]{REC_DIRECTORY, MONTHLY_DIRECTORY}; - names = new String[intValues.length]; - names[0] = getString(R.string.store_tracks_in_rec_directory); - names[1] = getString(R.string.store_tracks_in_monthly_directories); -// names[2] = getString(R.string.store_tracks_in_daily_directories); - cat.addPreference(createListPreference(settings.TRACK_STORAGE_DIRECTORY, names, intValues, - R.string.track_storage_directory, R.string.track_storage_directory_descrp)); - } - - - private void createLiveSection(PreferenceScreen grp) { - PreferenceCategory cat; - cat = new PreferenceCategory(this); - cat.setTitle(R.string.live_monitoring_m); - grp.addPreference(cat); - - EditTextPreference urlPreference = createEditTextPreference(settings.LIVE_MONITORING_URL, R.string.live_monitoring_url, - R.string.live_monitoring_url_descr); - urlPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (Algorithms.isValidMessageFormat((String) newValue)) { - return SettingsMonitoringActivity.super.onPreferenceChange(preference, newValue); - } else { - Toast.makeText(SettingsMonitoringActivity.this, R.string.wrong_format, Toast.LENGTH_SHORT).show(); - return false; - } - } - }); - cat.addPreference(urlPreference); - final CheckBoxPreference liveMonitoring = createCheckBoxPreference(settings.LIVE_MONITORING, R.string.live_monitoring_m, - R.string.live_monitoring_m_descr); - cat.addPreference(liveMonitoring); - cat.addPreference(createTimeListPreference(settings.LIVE_MONITORING_INTERVAL, SECONDS, - MINUTES, 1000, R.string.live_monitoring_interval, R.string.live_monitoring_interval_descr)); - cat.addPreference(createTimeListPreference(settings.LIVE_MONITORING_MAX_INTERVAL_TO_SEND, null, - MAX_INTERVAL_TO_SEND_MINUTES, 1000, R.string.live_monitoring_max_interval_to_send, R.string.live_monitoring_max_interval_to_send_desrc)); - } - - private void createNotificationSection(PreferenceScreen grp) { - PreferenceCategory cat; - cat = new PreferenceCategory(this); - cat.setTitle(R.string.shared_string_notifications); - grp.addPreference(cat); - - final CheckBoxPreference tripRecording = createCheckBoxPreference(settings.SHOW_TRIP_REC_NOTIFICATION, R.string.trip_rec_notification_settings, - R.string.trip_rec_notification_settings_desc); - cat.addPreference(tripRecording); - } - - public void updateAllSettings() { - super.updateAllSettings(); - - if(routeServiceEnabled != null) { - routeServiceEnabled.setChecked(getMyApplication().getNavigationService() != null); - } - } - - private void saveCurrentTracks(final SavingTrackHelper helper) { - setProgressVisibility(true); - getMyApplication().getTaskManager().runInBackground(new OsmAndTaskRunnable() { - - @Override - protected Void doInBackground(Void... params) { - SavingTrackHelper helper = getMyApplication().getSavingTrackHelper(); - helper.saveDataToGpx(getMyApplication().getAppCustomization().getTracksDir()); - helper.close(); - return null; - } - @Override - protected void onPostExecute(Void result) { - setProgressVisibility(false); - } - - }, (Void) null); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if(broadcastReceiver != null) { - unregisterReceiver(broadcastReceiver); - broadcastReceiver = null; - } - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String prefId = preference.getKey(); - if (preference instanceof ListPreference) { - int ind = ((ListPreference) preference).findIndexOfValue((String) newValue); - CharSequence entry = ((ListPreference) preference).getEntries()[ind]; - Map map = getListPrefValues().get(prefId); - if (map != null) { - newValue = map.get(entry); - } - } - showConfirmDialog(prefId, newValue); - return false; - } - - protected void showConfirmDialog(final String prefId, final Object newValue) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - String appModeName = selectedAppMode.toHumanString(); - String currentModeText = getString(R.string.apply_to_current_profile, appModeName); - int start = currentModeText.indexOf(appModeName); - - SpannableString[] strings = new SpannableString[2]; - strings[0] = new SpannableString(getString(R.string.apply_to_all_profiles)); - strings[1] = new SpannableString(currentModeText); - strings[1].setSpan(new StyleSpan(Typeface.BOLD), start, start + appModeName.length(), 0); - - final int[] icons = new int[2]; - icons[0] = R.drawable.ic_action_copy; - icons[1] = selectedAppMode.getIconRes(); - - final boolean nightMode = !settings.isLightContent(); - final OsmandApplication app = getMyApplication(); - final LayoutInflater themedInflater = UiUtilities.getInflater(this, nightMode); - - //set up dialog title - View dialogTitle = themedInflater.inflate(R.layout.bottom_sheet_item_simple, null); - dialogTitle.findViewById(R.id.icon).setVisibility(View.GONE); - TextView tvTitle = dialogTitle.findViewById(R.id.title); - tvTitle.setText(R.string.change_default_settings); - int textSize = (int) app.getResources().getDimension(R.dimen.dialog_header_text_size); - tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - builder.setCustomTitle(dialogTitle); - - final ArrayAdapter singleChoiceAdapter = new ArrayAdapter(this, R.layout.bottom_sheet_item_simple, R.id.title, strings) { - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View v = convertView; - if (v == null) { - v = themedInflater.inflate(R.layout.bottom_sheet_item_simple, parent, false); - } - int activeColor = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; - Drawable icon = app.getUIUtilities().getIcon(icons[position], activeColor); - ((TextView) v.findViewById(R.id.title)).setText(getItem(position)); - ((ImageView) v.findViewById(R.id.icon)).setImageDrawable(icon); - return v; - } - }; - - builder.setAdapter(singleChoiceAdapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - settings.setPreferenceForAllModes(prefId, newValue); - } else { - settings.setPreference(prefId, newValue); - } - updateAllSettings(); - } - }); - - builder.setNegativeButton(R.string.discard_changes, null); - AlertDialog dialog = builder.create(); - dialog.getListView().setDividerHeight(0); - dialog.show(); - } -} \ No newline at end of file From 433b2b87392ab3a025ffdec020d8b9d720d8f2d6 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 01:06:35 +0300 Subject: [PATCH 118/123] Add OAuth prefs to new osm plugin settings screen --- OsmAnd/AndroidManifest.xml | 7 ++ .../osmand/plus/activities/MapActivity.java | 5 ++ .../net/osmand/plus/helpers/IntentHelper.java | 21 +++++ .../plus/osmedit/OsmEditingFragment.java | 83 +++++++++++++++++-- 4 files changed, 111 insertions(+), 5 deletions(-) diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 3f3113ff91..ba270ee9bd 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -466,6 +466,13 @@ + + + + + + + diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 53d3fcf473..8cb6e832c5 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -118,6 +118,7 @@ import net.osmand.plus.measurementtool.GpxData; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; +import net.osmand.plus.osmedit.OsmEditingFragment; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -2216,6 +2217,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(GpxApproximationFragment.TAG); } + public OsmEditingFragment getOsmEditingFragment() { + return getFragment(SettingsScreenType.OPEN_STREET_MAP_EDITING.fragmentName); + } + public SnapTrackWarningFragment getSnapTrackWarningBottomSheet() { return getFragment(SnapTrackWarningFragment.TAG); } diff --git a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java index 161ef5ccde..19748b100c 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java @@ -19,6 +19,7 @@ import net.osmand.plus.activities.PluginsFragment; import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; import net.osmand.plus.mapmarkers.MapMarkersDialogFragment; import net.osmand.plus.mapsource.EditMapSourceDialogFragment; +import net.osmand.plus.osmedit.OsmEditingFragment; import net.osmand.plus.search.QuickSearchDialogFragment; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; @@ -59,6 +60,9 @@ public class IntentHelper { if (!applied) { applied = parseSendIntent(); } + if (!applied) { + applied = parseOAuthIntent(); + } return applied; } @@ -284,6 +288,23 @@ public class IntentHelper { return false; } + private boolean parseOAuthIntent() { + Intent intent = mapActivity.getIntent(); + if (intent != null && intent.getData() != null) { + Uri uri = intent.getData(); + if (uri.toString().startsWith("osmand-oauth")) { + OsmEditingFragment fragment = mapActivity.getOsmEditingFragment(); + if (fragment != null) { + String oauthVerifier = uri.getQueryParameter("oauth_verifier"); + fragment.authorize(oauthVerifier); + mapActivity.setIntent(null); + return true; + } + } + } + return false; + } + private boolean handleSendText(Intent intent) { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (!Algorithms.isEmpty(sharedText)) { diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java index 4c5ed73b07..daaeb6d034 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OsmEditingFragment.java @@ -1,5 +1,6 @@ package net.osmand.plus.osmedit; +import android.content.Context; import android.content.Intent; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -7,31 +8,50 @@ import android.os.Bundle; import android.text.SpannableString; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; +import net.osmand.PlatformUtil; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter; +import net.osmand.plus.settings.backend.OsmAndAppCustomization; +import net.osmand.plus.settings.bottomsheets.OsmLoginDataBottomSheet; import net.osmand.plus.settings.fragments.BaseSettingsFragment; import net.osmand.plus.settings.fragments.OnPreferenceChanged; -import net.osmand.plus.settings.bottomsheets.OsmLoginDataBottomSheet; import net.osmand.plus.settings.preferences.SwitchPreferenceEx; import net.osmand.plus.widgets.style.CustomTypefaceSpan; +import org.apache.commons.logging.Log; + import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID; import static net.osmand.plus.osmedit.OsmEditingPlugin.OSM_EDIT_TAB; public class OsmEditingFragment extends BaseSettingsFragment implements OnPreferenceChanged { + private static final Log log = PlatformUtil.getLog(OsmEditingFragment.class); + private static final String OSM_EDITING_INFO = "osm_editing_info"; private static final String OPEN_OSM_EDITS = "open_osm_edits"; private static final String OSM_LOGIN_DATA = "osm_login_data"; + private static final String OSM_OAUTH_SUCCESS = "osm_oauth_success"; + private static final String OSM_OAUTH_CLEAR = "osm_oauth_clear"; + private static final String OSM_OAUTH_LOGIN = "osm_oauth_login"; + + private OsmOAuthAuthorizationAdapter client; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + client = new OsmOAuthAuthorizationAdapter(app); + } @Override protected void setupPreferences() { @@ -42,6 +62,7 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer setupOfflineEditingPref(); setupOsmEditsDescrPref(); setupOsmEditsPref(); + setupOAuthPrefs(); } @Override @@ -73,7 +94,7 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer Drawable enabled = getActiveIcon(R.drawable.ic_world_globe_dark); Drawable icon = getPersistentPrefIcon(enabled, disabled); - SwitchPreferenceEx offlineEditingPref = (SwitchPreferenceEx) findPreference(settings.OFFLINE_EDITION.getId()); + SwitchPreferenceEx offlineEditingPref = findPreference(settings.OFFLINE_EDITION.getId()); offlineEditingPref.setDescription(getString(R.string.offline_edition_descr)); offlineEditingPref.setIcon(icon); } @@ -101,9 +122,37 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer createProfile.setIcon(getActiveIcon(R.drawable.ic_action_folder)); } + private void setupOAuthPrefs() { + Context ctx = getContext(); + if (ctx != null) { + PreferenceScreen screen = getPreferenceScreen(); + if (client.isValidToken()) { + Preference prefOAuth = new Preference(ctx); + prefOAuth.setTitle(R.string.osm_authorization_success); + prefOAuth.setSummary(R.string.osm_authorization_success); + prefOAuth.setKey(OSM_OAUTH_SUCCESS); + + Preference prefClearToken = new Preference(ctx); + prefClearToken.setTitle(R.string.shared_string_logoff); + prefClearToken.setSummary(R.string.clear_osm_token); + prefClearToken.setKey(OSM_OAUTH_CLEAR); + + screen.addPreference(prefOAuth); + screen.addPreference(prefClearToken); + } else { + Preference prefOAuth = new Preference(ctx); + prefOAuth.setTitle(R.string.perform_oauth_authorization); + prefOAuth.setSummary(R.string.perform_oauth_authorization_description); + prefOAuth.setKey(OSM_OAUTH_LOGIN); + screen.addPreference(prefOAuth); + } + } + } + @Override public boolean onPreferenceClick(Preference preference) { - if (OPEN_OSM_EDITS.equals(preference.getKey())) { + String prefId = preference.getKey(); + if (OPEN_OSM_EDITS.equals(prefId)) { Bundle bundle = new Bundle(); bundle.putInt(TAB_ID, OSM_EDIT_TAB); @@ -113,12 +162,29 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer favorites.putExtra(MapActivity.INTENT_PARAMS, bundle); startActivity(favorites); return true; - } else if (OSM_LOGIN_DATA.equals(preference.getKey())) { + } else if (OSM_LOGIN_DATA.equals(prefId)) { FragmentManager fragmentManager = getFragmentManager(); if (fragmentManager != null) { OsmLoginDataBottomSheet.showInstance(fragmentManager, OSM_LOGIN_DATA, this, false, getSelectedAppMode()); return true; } + } else if (OSM_OAUTH_CLEAR.equals(prefId)) { + settings.USER_ACCESS_TOKEN.set(""); + settings.USER_ACCESS_TOKEN_SECRET.set(""); + + client.resetToken(); + client = new OsmOAuthAuthorizationAdapter(app); + + app.showShortToastMessage(R.string.osm_edit_logout_success); + updateAllSettings(); + return true; + } else if (OSM_OAUTH_LOGIN.equals(prefId)) { + View view = getView(); + if (view != null) { + ViewGroup appBarLayout = view.findViewById(R.id.appbar); + client.startOAuth(appBarLayout); + } + return true; } return super.onPreferenceClick(preference); } @@ -130,4 +196,11 @@ public class OsmEditingFragment extends BaseSettingsFragment implements OnPrefer nameAndPasswordPref.setSummary(settings.USER_NAME.get()); } } + + public void authorize(String oauthVerifier) { + if (client != null) { + client.authorize(oauthVerifier); + } + updateAllSettings(); + } } \ No newline at end of file From d6bc4566000be3929c3c083dc120326533b3e210 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 01:25:01 +0300 Subject: [PATCH 119/123] Remove outdated osm plugin activity --- OsmAnd/AndroidManifest.xml | 11 - .../osmedit/SettingsOsmEditingActivity.java | 189 ------------------ .../osmedit/ValidateOsmLoginDetailsTask.java | 34 ++++ .../bottomsheets/OsmLoginDataBottomSheet.java | 4 +- 4 files changed, 36 insertions(+), 202 deletions(-) delete mode 100644 OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java create mode 100644 OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index ba270ee9bd..43497808c4 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -486,17 +486,6 @@ - - - - - - - - - diff --git a/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java b/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java deleted file mode 100644 index 1be623825b..0000000000 --- a/OsmAnd/src/net/osmand/plus/osmedit/SettingsOsmEditingActivity.java +++ /dev/null @@ -1,189 +0,0 @@ -package net.osmand.plus.osmedit; - - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.StrictMode; -import android.preference.CheckBoxPreference; -import android.preference.DialogPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceScreen; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; -import com.github.scribejava.core.model.OAuthAsyncRequestCallback; -import com.github.scribejava.core.model.Response; -import net.osmand.PlatformUtil; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.R; -import net.osmand.plus.activities.SettingsBaseActivity; -import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter; -import net.osmand.plus.settings.backend.OsmAndAppCustomization; -import org.apache.commons.logging.Log; - -import java.io.IOException; - -public class SettingsOsmEditingActivity extends SettingsBaseActivity { - private OsmOAuthAuthorizationAdapter client; - private static final Log log = PlatformUtil.getLog(SettingsOsmEditingActivity.class); - - @Override - public void onCreate(Bundle savedInstanceState) { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectNetwork() - .penaltyLog() - .build()); - - ((OsmandApplication) getApplication()).applyTheme(this); - super.onCreate(savedInstanceState); - - client = new OsmOAuthAuthorizationAdapter(getMyApplication()); - - getToolbar().setTitle(R.string.osm_settings); - @SuppressWarnings("deprecation") - PreferenceScreen grp = getPreferenceScreen(); - - DialogPreference loginDialogPreference = new OsmLoginDataDialogPreference(this, null); - grp.addPreference(loginDialogPreference); - - CheckBoxPreference poiEdit = createCheckBoxPreference(settings.OFFLINE_EDITION, - R.string.offline_edition, R.string.offline_edition_descr); - grp.addPreference(poiEdit); - - final Preference pref = new Preference(this); - pref.setTitle(R.string.local_openstreetmap_settings); - pref.setSummary(R.string.local_openstreetmap_settings_descr); - pref.setKey("local_openstreetmap_points"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - OsmAndAppCustomization appCustomization = getMyApplication().getAppCustomization(); - final Intent favorites = new Intent(SettingsOsmEditingActivity.this, - appCustomization.getFavoritesActivity()); - favorites.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - getMyApplication().getSettings().FAVORITES_TAB.set(R.string.osm_edits); - startActivity(favorites); - return true; - } - }); - grp.addPreference(pref); - - final Preference prefOAuth = new Preference(this); - if (client.isValidToken()){ - prefOAuth.setTitle(R.string.osm_authorization_success); - prefOAuth.setSummary(R.string.osm_authorization_success); - prefOAuth.setKey("local_openstreetmap_oauth_success"); - final Preference prefClearToken = new Preference(this); - prefClearToken.setTitle(R.string.shared_string_logoff); - prefClearToken.setSummary(R.string.clear_osm_token); - prefClearToken.setKey("local_openstreetmap_oauth_clear"); - prefClearToken.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - settings.USER_ACCESS_TOKEN.set(""); - settings.USER_ACCESS_TOKEN_SECRET.set(""); - client.resetToken(); - Toast.makeText(SettingsOsmEditingActivity.this, R.string.osm_edit_logout_success, Toast.LENGTH_SHORT).show(); - finish(); - startActivity(getIntent()); - return true; - } - }); - grp.addPreference(prefClearToken); - } - else { - prefOAuth.setTitle(R.string.perform_oauth_authorization); - prefOAuth.setSummary(R.string.perform_oauth_authorization_description); - prefOAuth.setKey("local_openstreetmap_oauth_login"); - prefOAuth.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - ViewGroup preferenceView = (ViewGroup)getListView().getChildAt(preference.getOrder()); - client.startOAuth(preferenceView); - return true; - } - }); - } - grp.addPreference(prefOAuth); - } - - public class OsmLoginDataDialogPreference extends DialogPreference { - private TextView userNameEditText; - private TextView passwordEditText; - - public OsmLoginDataDialogPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - setDialogLayoutResource(R.layout.osm_user_login_details); - setPositiveButtonText(android.R.string.ok); - setNegativeButtonText(android.R.string.cancel); - setDialogTitle(R.string.open_street_map_login_and_pass); - - setTitle(R.string.open_street_map_login_and_pass); - setSummary(R.string.open_street_map_login_descr); - - setDialogIcon(null); - } - - @Override - protected void onBindDialogView(View view) { - userNameEditText = (TextView) view.findViewById(R.id.user_name_field); - userNameEditText.setText(settings.USER_NAME.get()); - passwordEditText = (TextView) view.findViewById(R.id.password_field); - passwordEditText.setText(settings.USER_PASSWORD.get()); - super.onBindDialogView(view); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (positiveResult) { - settings.USER_NAME.set(userNameEditText.getText().toString()); - settings.USER_PASSWORD.set(passwordEditText.getText().toString()); - new ValidateOsmLoginDetailsTask(SettingsOsmEditingActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - } - - public static class ValidateOsmLoginDetailsTask extends AsyncTask { - private final Context context; - - public ValidateOsmLoginDetailsTask(Context context) { - this.context = context; - } - - @Override - protected OsmBugsUtil.OsmBugResult doInBackground(Void... params) { - OsmEditingPlugin plugin = OsmandPlugin.getPlugin(OsmEditingPlugin.class); - assert plugin != null; - OsmBugsRemoteUtil remoteUtil = plugin.getOsmNotesRemoteUtil(); - return remoteUtil.validateLoginDetails(); - } - - @Override - protected void onPostExecute(OsmBugsUtil.OsmBugResult osmBugResult) { - String text = osmBugResult.warning != null ? osmBugResult.warning : context.getString(R.string.osm_authorization_success); - Toast.makeText(context, text, Toast.LENGTH_LONG).show(); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - Uri uri = intent.getData(); - if (uri != null && uri.toString().startsWith("osmand-oauth")) { - String oauthVerifier = uri.getQueryParameter("oauth_verifier"); - client.authorize(oauthVerifier); - finish(); - startActivity(getIntent()); - } - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java b/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java new file mode 100644 index 0000000000..722f104c51 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/ValidateOsmLoginDetailsTask.java @@ -0,0 +1,34 @@ +package net.osmand.plus.osmedit; + +import android.os.AsyncTask; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.osmedit.OsmBugsUtil.OsmBugResult; + +public class ValidateOsmLoginDetailsTask extends AsyncTask { + + private OsmandApplication app; + + public ValidateOsmLoginDetailsTask(OsmandApplication app) { + this.app = app; + } + + @Override + protected OsmBugResult doInBackground(Void... params) { + OsmEditingPlugin plugin = OsmandPlugin.getPlugin(OsmEditingPlugin.class); + assert plugin != null; + OsmBugsRemoteUtil remoteUtil = plugin.getOsmNotesRemoteUtil(); + return remoteUtil.validateLoginDetails(); + } + + @Override + protected void onPostExecute(OsmBugResult osmBugResult) { + if (osmBugResult.warning != null) { + app.showToastMessage(osmBugResult.warning); + } else { + app.showToastMessage(R.string.osm_authorization_success); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java index 37e993cf43..2b7494f0a4 100644 --- a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/OsmLoginDataBottomSheet.java @@ -12,13 +12,13 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; +import net.osmand.plus.osmedit.ValidateOsmLoginDetailsTask; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem; import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; -import net.osmand.plus.osmedit.SettingsOsmEditingActivity; import net.osmand.plus.settings.fragments.OnPreferenceChanged; public class OsmLoginDataBottomSheet extends BasePreferenceBottomSheet { @@ -84,7 +84,7 @@ public class OsmLoginDataBottomSheet extends BasePreferenceBottomSheet { app.getSettings().USER_NAME.set(userNameEditText.getText().toString()); app.getSettings().USER_PASSWORD.set(passwordEditText.getText().toString()); - new SettingsOsmEditingActivity.ValidateOsmLoginDetailsTask(app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new ValidateOsmLoginDetailsTask(app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); Fragment target = getTargetFragment(); Preference preference = getPreference(); From 4f5f22fdc24db81d766f3020cebf59a9a34bd954 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 10:34:11 +0300 Subject: [PATCH 120/123] fix empty space --- OsmAnd/res/layout/plugins_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/layout/plugins_list_item.xml b/OsmAnd/res/layout/plugins_list_item.xml index be5e2e575a..4e8f369e0c 100644 --- a/OsmAnd/res/layout/plugins_list_item.xml +++ b/OsmAnd/res/layout/plugins_list_item.xml @@ -52,7 +52,7 @@ android:ellipsize="end" android:lines="2" android:maxLines="2" - android:scrollbars="none" + android:scrollbars="none" android:text="@string/lorem_ipsum" android:textColor="?android:textColorSecondary" android:textSize="@dimen/default_desc_text_size" From 799d6481b6d7c90bf77fd6bf0a549948c8722308 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 10:38:16 +0300 Subject: [PATCH 121/123] remove unnecessary changes --- OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java | 1 - 1 file changed, 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java index 09cdaad519..294b382ca4 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java @@ -974,7 +974,6 @@ public class MapActivityActions implements DialogProvider { @Override public boolean onContextMenuClick(ArrayAdapter adapter, int itemId, int pos, boolean isChecked, int[] viewCoordinates) { app.logEvent("drawer_plugins_open"); - MapActivity.clearPrevActivityIntent(); PluginsFragment.showInstance(mapActivity.getSupportFragmentManager()); return true; } From 257d27277bc9e2ba7bca20c2e3e770acf8c58108 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 10:41:07 +0300 Subject: [PATCH 122/123] remove unnecessary changes --- .../net/osmand/plus/settings/backend/ExportSettingsType.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java index 79e35ffdf8..bd7e8e9f4e 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/ExportSettingsType.java @@ -8,9 +8,6 @@ public enum ExportSettingsType { CUSTOM_RENDER_STYLE, CUSTOM_ROUTING, AVOID_ROADS, - MARKERS, - FAVORITES, TRACKS, - MULTIMEDIA_NOTES, - OSM_CHANGES + MULTIMEDIA_NOTES } From 4272eaf93c5972e65ab6454567e879bf3ff7fee8 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Wed, 21 Oct 2020 11:43:17 +0300 Subject: [PATCH 123/123] remove unnecessary changes --- .../plus/settings/backend/backup/SettingsItemType.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java index 163e457e58..1a25e24817 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/SettingsItemType.java @@ -12,10 +12,5 @@ public enum SettingsItemType { MAP_SOURCES, AVOID_ROADS, SUGGESTED_DOWNLOADS, - DOWNLOADS, - MARKERS, - FAVORITES, - TRACKS, - AUDIO_VIDEO_NOTES, - OSM_CHANGES + DOWNLOADS } \ No newline at end of file