From a577b26e7f167d92c0a1c1f419e1ee1af1f2ea69 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Tue, 22 Sep 2020 16:00:37 +0300 Subject: [PATCH 01/75] Fix #9623 --- .../net/osmand/plus/ContextMenuAdapter.java | 19 ++++++- .../osmand/plus/dashboard/DashboardOnMap.java | 27 +++++++++- .../osmand/plus/dialogs/ConfigureMapMenu.java | 50 ++++++++++++++++--- .../osmand/plus/render/RendererRegistry.java | 31 +++++++++++- .../fragments/ConfigureMenuItemsFragment.java | 2 +- .../fragments/ConfigureMenuRootFragment.java | 2 +- 6 files changed, 119 insertions(+), 12 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java index 7263f119bd..3ea7eee4d7 100644 --- a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java +++ b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java @@ -65,6 +65,7 @@ public class ContextMenuAdapter { @LayoutRes private int DEFAULT_LAYOUT_ID = R.layout.list_menu_item_native; List items = new ArrayList<>(); + private ArrayAdapter arrayAdapter; private boolean profileDependent = false; private boolean nightMode; private ConfigureMapMenu.OnClickListener changeAppModeListener = null; @@ -100,6 +101,15 @@ public class ContextMenuAdapter { return items.get(position); } + public ContextMenuItem getItemById(@NonNull String id) { + for (ContextMenuItem item : items) { + if (id.equals(item.getId())) { + return item; + } + } + return null; + } + public List getItems() { return items; } @@ -192,8 +202,9 @@ public class ContextMenuAdapter { } } items.removeAll(itemsToRemove); - return new ContextMenuArrayAdapter(activity, layoutId, R.id.title, + this.arrayAdapter = new ContextMenuArrayAdapter(activity, layoutId, R.id.title, items.toArray(new ContextMenuItem[items.size()]), app, lightTheme, changeAppModeListener); + return this.arrayAdapter; } public class ContextMenuArrayAdapter extends ArrayAdapter { @@ -616,6 +627,12 @@ public class ContextMenuAdapter { return visible; } + public void notifyDataSetChanged() { + if (this.arrayAdapter != null) { + this.arrayAdapter.notifyDataSetChanged(); + } + } + public static OnItemDeleteAction makeDeleteAction(final OsmandPreference... prefs) { return new OnItemDeleteAction() { @Override diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 4c9b2a2c2b..74d61c7479 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -130,6 +130,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo private ArrayAdapter listAdapter; private OnItemClickListener listAdapterOnClickListener; + private DashboardStateListener dashboardStateListener; private boolean visible = false; private DashboardType visibleType; @@ -567,7 +568,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo boolean appModeChanged = currentAppMode != previousAppMode; boolean refresh = this.visibleType == type && !appModeChanged; - previousAppMode = currentAppMode; + this.previousAppMode = currentAppMode; this.visibleType = type; DashboardOnMap.staticVisible = visible; DashboardOnMap.staticVisibleType = type; @@ -657,9 +658,21 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo settings.MAPILLARY_FIRST_DIALOG_SHOWN.set(true); } } + notifyDashboardVisibilityStateListener(visible); mapActivity.updateStatusBarColor(); } + private void notifyDashboardVisibilityStateListener(boolean visible) { + if (dashboardStateListener != null) { + if (visible) { + dashboardStateListener.onShowDashboard(); + } else { + dashboardStateListener.onHideDashboard(); + dashboardStateListener = null; + } + } + } + public void updateDashboard() { if (visibleType == DashboardType.ROUTE_PREFERENCES) { refreshContent(false); @@ -705,7 +718,9 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo if (visibleType == DashboardType.CONFIGURE_SCREEN) { cm = mapActivity.getMapLayers().getMapWidgetRegistry().getViewConfigureMenuAdapter(mapActivity); } else if (visibleType == DashboardType.CONFIGURE_MAP) { - cm = new ConfigureMapMenu().createListAdapter(mapActivity); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(mapActivity); + dashboardStateListener = configureMapMenu; + cm = configureMapMenu.createListAdapter(mapActivity); } else if (visibleType == DashboardType.LIST_MENU) { cm = mapActivity.getMapActions().createMainOptionsMenu(); } else if (visibleType == DashboardType.ROUTE_PREFERENCES) { @@ -1315,4 +1330,12 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo @Override public void routeWasFinished() { } + + public interface DashboardStateListener { + + void onShowDashboard(); + + void onHideDashboard(); + + } } diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index 647eebeb4c..900ada6a20 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -37,9 +37,11 @@ import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.SettingsActivity; +import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.inapp.InAppPurchaseHelper; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RendererRegistry; +import net.osmand.plus.render.RendererRegistry.OnChangeRenderingRuleListener; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings.ListStringPreference; @@ -98,8 +100,9 @@ import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_DETAI import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_HIDE; import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_ROUTES; -public class ConfigureMapMenu { +public class ConfigureMapMenu implements DashboardOnMap.DashboardStateListener { private static final Log LOG = PlatformUtil.getLog(ConfigureMapMenu.class); + public static final String TAG = ConfigureMapMenu.class.getName(); public static final String HIKING_ROUTES_OSMC_ATTR = "hikingRoutesOSMC"; public static final String CURRENT_TRACK_COLOR_ATTR = "currentTrackColor"; public static final String CURRENT_TRACK_WIDTH_ATTR = "currentTrackWidth"; @@ -110,10 +113,17 @@ public class ConfigureMapMenu { private int selectedLanguageIndex; private boolean transliterateNames; + private MapActivity mapActivity; + private ContextMenuAdapter contextMenuAdapter; + public interface OnClickListener { void onClick(); } + public ConfigureMapMenu(MapActivity mapActivity) { + this.mapActivity = mapActivity; + } + public ContextMenuAdapter createListAdapter(final MapActivity ma) { OsmandApplication app = ma.getMyApplication(); boolean nightMode = app.getDaynightHelper().isNightModeForMapControls(); @@ -136,6 +146,7 @@ public class ConfigureMapMenu { adapter.setNightMode(nightMode); createLayersItems(customRules, adapter, ma, themeRes, nightMode); createRenderingAttributeItems(customRules, adapter, ma, themeRes, nightMode); + this.contextMenuAdapter = adapter; return adapter; } @@ -273,13 +284,17 @@ public class ConfigureMapMenu { final OsmandSettings settings = app.getSettings(); final int selectedProfileColorRes = settings.APPLICATION_MODE.get().getIconColorInfo().getColor(nightMode); final int selectedProfileColor = ContextCompat.getColor(app, selectedProfileColorRes); + RendererRegistry rr = app.getRendererRegistry(); + RenderingRulesStorage storage = rr.getCurrentSelectedRenderer(); + String renderDescr = getRenderDescr(activity, storage); adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_map_rendering, activity) .setId(MAP_RENDERING_CATEGORY_ID) .setCategory(true).setLayout(R.layout.list_group_title_with_switch).createItem()); adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_renderer, activity) .setId(MAP_STYLE_ID) - .setDescription(getRenderDescr(activity)).setLayout(R.layout.list_item_single_line_descrition_narrow) + .setDescription(renderDescr) + .setLayout(R.layout.list_item_single_line_descrition_narrow) .setIcon(R.drawable.ic_map).setListener(new ContextMenuAdapter.ItemClickListener() { @Override public boolean onContextMenuClick(final ArrayAdapter ad, int itemId, @@ -942,13 +957,11 @@ public class ConfigureMapMenu { dialog.show(); } - protected String getRenderDescr(final MapActivity activity) { - RendererRegistry rr = activity.getMyApplication().getRendererRegistry(); - RenderingRulesStorage storage = rr.getCurrentSelectedRenderer(); + protected String getRenderDescr(Context ctx, RenderingRulesStorage storage) { if (storage == null) { return ""; } - String translation = RendererRegistry.getTranslatedRendererName(activity, storage.getName()); + String translation = RendererRegistry.getTranslatedRendererName(ctx, storage.getName()); return translation == null ? storage.getName() : translation; } @@ -1109,6 +1122,31 @@ public class ConfigureMapMenu { } } + @Override + public void onShowDashboard() { + getMyApplication().getRendererRegistry() + .addOnChangeRenderingRuleListener(TAG, new OnChangeRenderingRuleListener() { + @Override + public void onRenderingRuleChanged(RenderingRulesStorage currentSelectedRender) { + if (contextMenuAdapter != null) { + ContextMenuItem item = contextMenuAdapter.getItemById(MAP_STYLE_ID); + String renderDescr = getRenderDescr(mapActivity, currentSelectedRender); + item.setDescription(renderDescr); + contextMenuAdapter.notifyDataSetChanged(); + } + } + }); + } + + @Override + public void onHideDashboard() { + getMyApplication().getRendererRegistry().removeOnChangeRenderingRuleListener(TAG); + } + + private OsmandApplication getMyApplication() { + return mapActivity.getMyApplication(); + } + private static class StringSpinnerArrayAdapter extends ArrayAdapter { private boolean nightMode; diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index 17dc572bc4..e20d90c27a 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -25,6 +25,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -49,6 +50,7 @@ public class RendererRegistry { private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; + private Map onChangeRenderingRuleListeners = new HashMap<>(); private Map externalRenderers = new LinkedHashMap(); private Map internalRenderers = new LinkedHashMap(); @@ -334,8 +336,22 @@ public class RendererRegistry { return currentSelectedRender; } - public void setCurrentSelectedRender(RenderingRulesStorage currentSelectedRender) { + public void setCurrentSelectedRender(final RenderingRulesStorage currentSelectedRender) { this.currentSelectedRender = currentSelectedRender; + notifyChangeRenderRuleListeners(); + } + + public void notifyChangeRenderRuleListeners() { + app.runInUIThread(new Runnable() { + @Override + public void run() { + for (OnChangeRenderingRuleListener listener : onChangeRenderingRuleListeners.values()) { + if (listener != null) { + listener.onRenderingRuleChanged(currentSelectedRender); + } + } + } + }); } public void setRendererLoadedEventListener(IRendererLoadedEventListener listener) { @@ -361,4 +377,17 @@ public class RendererRegistry { public Map getExternalRenderers() { return externalRenderers; } + + public void addOnChangeRenderingRuleListener(@NonNull String key, + @NonNull OnChangeRenderingRuleListener listener) { + onChangeRenderingRuleListeners.put(key, listener); + } + + public void removeOnChangeRenderingRuleListener(@NonNull String key) { + onChangeRenderingRuleListeners.remove(key); + } + + public interface OnChangeRenderingRuleListener { + void onRenderingRuleChanged(RenderingRulesStorage currentSelectedRender); + } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java index 44fb4bf7f6..43cd0d59eb 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java @@ -197,7 +197,7 @@ public class ConfigureMenuItemsFragment extends BaseOsmAndFragment contextMenuAdapter = ((MapActivity) activity).getMapActions().createMainOptionsMenu(); break; case CONFIGURE_MAP: - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity)activity); contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); break; case CONTEXT_MENU_ACTIONS: diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java index 330f1c0a18..b47a761e8b 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java @@ -285,7 +285,7 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { contextMenuAdapter = mapActivityActions.createMainOptionsMenu(); break; case CONFIGURE_MAP: - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); break; case CONTEXT_MENU_ACTIONS: From 2e388a060658fd52c3ad3e3e630fc446cdcf9b6d Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 24 Sep 2020 11:17:40 +0300 Subject: [PATCH 02/75] Fix #9623 part 2 (Refactoring) --- .../net/osmand/plus/ContextMenuAdapter.java | 8 ++-- .../osmand/plus/activities/MapActivity.java | 4 ++ .../osmand/plus/dashboard/DashboardOnMap.java | 35 +++++++--------- .../osmand/plus/dialogs/ConfigureMapMenu.java | 41 +++++++------------ .../osmand/plus/render/RendererRegistry.java | 27 +----------- 5 files changed, 38 insertions(+), 77 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java index 3ea7eee4d7..795bc7224c 100644 --- a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java +++ b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java @@ -202,9 +202,9 @@ public class ContextMenuAdapter { } } items.removeAll(itemsToRemove); - this.arrayAdapter = new ContextMenuArrayAdapter(activity, layoutId, R.id.title, + arrayAdapter = new ContextMenuArrayAdapter(activity, layoutId, R.id.title, items.toArray(new ContextMenuItem[items.size()]), app, lightTheme, changeAppModeListener); - return this.arrayAdapter; + return arrayAdapter; } public class ContextMenuArrayAdapter extends ArrayAdapter { @@ -628,8 +628,8 @@ public class ContextMenuAdapter { } public void notifyDataSetChanged() { - if (this.arrayAdapter != null) { - this.arrayAdapter.notifyDataSetChanged(); + if (arrayAdapter != null) { + arrayAdapter.notifyDataSetChanged(); } } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index e23e596dcf..2816e7d5b0 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -1466,6 +1466,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven } protected void onPostExecute(Void result) { + DashboardOnMap dashboard = getDashboard(); + if (dashboard != null) { + dashboard.onMapSettingsUpdated(); + } } }.executeOnExecutor(singleThreadExecutor, (Void) null); diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 74d61c7479..127837c7be 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -96,6 +96,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_STYLE_ID; + public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInformationListener { private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(DashboardOnMap.class); @@ -130,7 +132,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo private ArrayAdapter listAdapter; private OnItemClickListener listAdapterOnClickListener; - private DashboardStateListener dashboardStateListener; + private ConfigureMapMenu configureMapMenu; private boolean visible = false; private DashboardType visibleType; @@ -657,20 +659,14 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo fragment.show(mapActivity.getSupportFragmentManager(), MapillaryFirstDialogFragment.TAG); settings.MAPILLARY_FIRST_DIALOG_SHOWN.set(true); } + + deleteTmpReferences(); } - notifyDashboardVisibilityStateListener(visible); mapActivity.updateStatusBarColor(); } - private void notifyDashboardVisibilityStateListener(boolean visible) { - if (dashboardStateListener != null) { - if (visible) { - dashboardStateListener.onShowDashboard(); - } else { - dashboardStateListener.onHideDashboard(); - dashboardStateListener = null; - } - } + private void deleteTmpReferences() { + configureMapMenu = null; } public void updateDashboard() { @@ -718,8 +714,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo if (visibleType == DashboardType.CONFIGURE_SCREEN) { cm = mapActivity.getMapLayers().getMapWidgetRegistry().getViewConfigureMenuAdapter(mapActivity); } else if (visibleType == DashboardType.CONFIGURE_MAP) { - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(mapActivity); - dashboardStateListener = configureMapMenu; + configureMapMenu = new ConfigureMapMenu(mapActivity); cm = configureMapMenu.createListAdapter(mapActivity); } else if (visibleType == DashboardType.LIST_MENU) { cm = mapActivity.getMapActions().createMainOptionsMenu(); @@ -1047,6 +1042,12 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo } } + public void onMapSettingsUpdated() { + if (configureMapMenu != null) { + configureMapMenu.updateMenuItem(MAP_STYLE_ID); + } + } + public void updateLocation(final boolean centerChanged, final boolean locationChanged, final boolean compassChanged) { if (inLocationUpdate) { @@ -1330,12 +1331,4 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo @Override public void routeWasFinished() { } - - public interface DashboardStateListener { - - void onShowDashboard(); - - void onHideDashboard(); - - } } diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index 900ada6a20..a48421e373 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -37,11 +37,9 @@ import net.osmand.plus.R; import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.SettingsActivity; -import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.inapp.InAppPurchaseHelper; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RendererRegistry; -import net.osmand.plus.render.RendererRegistry.OnChangeRenderingRuleListener; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings.ListStringPreference; @@ -100,7 +98,7 @@ import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_DETAI import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_HIDE; import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_ROUTES; -public class ConfigureMapMenu implements DashboardOnMap.DashboardStateListener { +public class ConfigureMapMenu { private static final Log LOG = PlatformUtil.getLog(ConfigureMapMenu.class); public static final String TAG = ConfigureMapMenu.class.getName(); public static final String HIKING_ROUTES_OSMC_ATTR = "hikingRoutesOSMC"; @@ -284,9 +282,7 @@ public class ConfigureMapMenu implements DashboardOnMap.DashboardStateListener { final OsmandSettings settings = app.getSettings(); final int selectedProfileColorRes = settings.APPLICATION_MODE.get().getIconColorInfo().getColor(nightMode); final int selectedProfileColor = ContextCompat.getColor(app, selectedProfileColorRes); - RendererRegistry rr = app.getRendererRegistry(); - RenderingRulesStorage storage = rr.getCurrentSelectedRenderer(); - String renderDescr = getRenderDescr(activity, storage); + String renderDescr = getRenderDescr(app); adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_map_rendering, activity) .setId(MAP_RENDERING_CATEGORY_ID) @@ -957,11 +953,13 @@ public class ConfigureMapMenu implements DashboardOnMap.DashboardStateListener { dialog.show(); } - protected String getRenderDescr(Context ctx, RenderingRulesStorage storage) { + protected String getRenderDescr(OsmandApplication app) { + RendererRegistry rr = app.getRendererRegistry(); + RenderingRulesStorage storage = rr.getCurrentSelectedRenderer(); if (storage == null) { return ""; } - String translation = RendererRegistry.getTranslatedRendererName(ctx, storage.getName()); + String translation = RendererRegistry.getTranslatedRendererName(app, storage.getName()); return translation == null ? storage.getName() : translation; } @@ -1122,25 +1120,16 @@ public class ConfigureMapMenu implements DashboardOnMap.DashboardStateListener { } } - @Override - public void onShowDashboard() { - getMyApplication().getRendererRegistry() - .addOnChangeRenderingRuleListener(TAG, new OnChangeRenderingRuleListener() { - @Override - public void onRenderingRuleChanged(RenderingRulesStorage currentSelectedRender) { - if (contextMenuAdapter != null) { - ContextMenuItem item = contextMenuAdapter.getItemById(MAP_STYLE_ID); - String renderDescr = getRenderDescr(mapActivity, currentSelectedRender); - item.setDescription(renderDescr); - contextMenuAdapter.notifyDataSetChanged(); - } - } - }); - } + public void updateMenuItem(String itemId) { + OsmandApplication app = getMyApplication(); + if (app == null) return; - @Override - public void onHideDashboard() { - getMyApplication().getRendererRegistry().removeOnChangeRenderingRuleListener(TAG); + if (MAP_STYLE_ID.equals(itemId) && contextMenuAdapter != null) { + ContextMenuItem item = contextMenuAdapter.getItemById(MAP_STYLE_ID); + String renderDescr = getRenderDescr(app); + item.setDescription(renderDescr); + contextMenuAdapter.notifyDataSetChanged(); + } } private OsmandApplication getMyApplication() { diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index e20d90c27a..b96a2dbd5e 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -25,7 +25,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -50,8 +49,7 @@ public class RendererRegistry { private RenderingRulesStorage defaultRender = null; private RenderingRulesStorage currentSelectedRender = null; - private Map onChangeRenderingRuleListeners = new HashMap<>(); - + private Map externalRenderers = new LinkedHashMap(); private Map internalRenderers = new LinkedHashMap(); @@ -338,20 +336,6 @@ public class RendererRegistry { public void setCurrentSelectedRender(final RenderingRulesStorage currentSelectedRender) { this.currentSelectedRender = currentSelectedRender; - notifyChangeRenderRuleListeners(); - } - - public void notifyChangeRenderRuleListeners() { - app.runInUIThread(new Runnable() { - @Override - public void run() { - for (OnChangeRenderingRuleListener listener : onChangeRenderingRuleListeners.values()) { - if (listener != null) { - listener.onRenderingRuleChanged(currentSelectedRender); - } - } - } - }); } public void setRendererLoadedEventListener(IRendererLoadedEventListener listener) { @@ -378,15 +362,6 @@ public class RendererRegistry { return externalRenderers; } - public void addOnChangeRenderingRuleListener(@NonNull String key, - @NonNull OnChangeRenderingRuleListener listener) { - onChangeRenderingRuleListeners.put(key, listener); - } - - public void removeOnChangeRenderingRuleListener(@NonNull String key) { - onChangeRenderingRuleListeners.remove(key); - } - public interface OnChangeRenderingRuleListener { void onRenderingRuleChanged(RenderingRulesStorage currentSelectedRender); } From 7534f8b28bcdaf24006e6e3caff2f88316effc0d Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 24 Sep 2020 11:25:04 +0300 Subject: [PATCH 03/75] Fix #9623 part 3 (delete useless code) --- OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java | 1 - OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 4 ---- .../plus/settings/fragments/ConfigureMenuItemsFragment.java | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index a48421e373..44699c2451 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -100,7 +100,6 @@ import static net.osmand.render.RenderingRuleStorageProperties.UI_CATEGORY_ROUTE public class ConfigureMapMenu { private static final Log LOG = PlatformUtil.getLog(ConfigureMapMenu.class); - public static final String TAG = ConfigureMapMenu.class.getName(); public static final String HIKING_ROUTES_OSMC_ATTR = "hikingRoutesOSMC"; public static final String CURRENT_TRACK_COLOR_ATTR = "currentTrackColor"; public static final String CURRENT_TRACK_WIDTH_ATTR = "currentTrackWidth"; diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index b96a2dbd5e..d382fa9882 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -361,8 +361,4 @@ public class RendererRegistry { public Map getExternalRenderers() { return externalRenderers; } - - public interface OnChangeRenderingRuleListener { - void onRenderingRuleChanged(RenderingRulesStorage currentSelectedRender); - } } diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java index 43cd0d59eb..731ffc2c38 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java @@ -197,7 +197,7 @@ public class ConfigureMenuItemsFragment extends BaseOsmAndFragment contextMenuAdapter = ((MapActivity) activity).getMapActions().createMainOptionsMenu(); break; case CONFIGURE_MAP: - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity)activity); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); break; case CONTEXT_MENU_ACTIONS: From b8a10322956cf1fbe8349bc0767c958b45166120 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 24 Sep 2020 11:29:49 +0300 Subject: [PATCH 04/75] Fix #9623 part 4 (remove tautology) --- .../src/net/osmand/plus/dashboard/DashboardOnMap.java | 2 +- .../src/net/osmand/plus/dialogs/ConfigureMapMenu.java | 10 +++++----- .../settings/fragments/ConfigureMenuItemsFragment.java | 2 +- .../settings/fragments/ConfigureMenuRootFragment.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 127837c7be..4c2c4a6a8d 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -715,7 +715,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo cm = mapActivity.getMapLayers().getMapWidgetRegistry().getViewConfigureMenuAdapter(mapActivity); } else if (visibleType == DashboardType.CONFIGURE_MAP) { configureMapMenu = new ConfigureMapMenu(mapActivity); - cm = configureMapMenu.createListAdapter(mapActivity); + cm = configureMapMenu.createListAdapter(); } else if (visibleType == DashboardType.LIST_MENU) { cm = mapActivity.getMapActions().createMainOptionsMenu(); } else if (visibleType == DashboardType.ROUTE_PREFERENCES) { diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index 44699c2451..d9a60adff3 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -110,7 +110,7 @@ public class ConfigureMapMenu { private int selectedLanguageIndex; private boolean transliterateNames; - private MapActivity mapActivity; + private MapActivity ma; private ContextMenuAdapter contextMenuAdapter; public interface OnClickListener { @@ -118,10 +118,10 @@ public class ConfigureMapMenu { } public ConfigureMapMenu(MapActivity mapActivity) { - this.mapActivity = mapActivity; + this.ma = mapActivity; } - public ContextMenuAdapter createListAdapter(final MapActivity ma) { + public ContextMenuAdapter createListAdapter() { OsmandApplication app = ma.getMyApplication(); boolean nightMode = app.getDaynightHelper().isNightModeForMapControls(); int themeRes = nightMode ? R.style.OsmandDarkTheme : R.style.OsmandLightTheme; @@ -134,7 +134,7 @@ public class ConfigureMapMenu { adapter.setChangeAppModeListener(new OnClickListener() { @Override public void onClick() { - ma.getDashboard().updateListAdapter(createListAdapter(ma)); + ma.getDashboard().updateListAdapter(createListAdapter()); } }); List customRules = getCustomRules(app, @@ -1132,7 +1132,7 @@ public class ConfigureMapMenu { } private OsmandApplication getMyApplication() { - return mapActivity.getMyApplication(); + return ma.getMyApplication(); } private static class StringSpinnerArrayAdapter extends ArrayAdapter { diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java index 731ffc2c38..926264aceb 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java @@ -198,7 +198,7 @@ public class ConfigureMenuItemsFragment extends BaseOsmAndFragment break; case CONFIGURE_MAP: ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); - contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); + contextMenuAdapter = configureMapMenu.createListAdapter(); break; case CONTEXT_MENU_ACTIONS: MapContextMenu menu = ((MapActivity) activity).getContextMenu(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java index b47a761e8b..f7b2dda412 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java @@ -286,7 +286,7 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { break; case CONFIGURE_MAP: ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); - contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); + contextMenuAdapter = configureMapMenu.createListAdapter(); break; case CONTEXT_MENU_ACTIONS: MapContextMenu menu = ((MapActivity) activity).getContextMenu(); From df56eaa22601332a5682a1da9654feee1ee8de47 Mon Sep 17 00:00:00 2001 From: max-klaus Date: Mon, 28 Sep 2020 14:15:29 +0300 Subject: [PATCH 05/75] WIP huawei subscriptions --- OsmAnd/.gitignore | 1 + OsmAnd/AndroidManifest-freehuawei.xml | 32 +- OsmAnd/AndroidManifest-huawei.xml | 25 - OsmAnd/build.gradle | 89 +-- .../plus/inapp/InAppPurchaseHelperImpl.java | 546 ++++++++++++++ .../plus/inapp/InAppPurchaseHelperImpl.java | 475 ++++++++++++ .../src/net/osmand/plus/AppInitializer.java | 4 +- .../src/net/osmand/plus/HuaweiDrmHelper.java | 66 -- OsmAnd/src/net/osmand/plus/Version.java | 4 +- .../osmand/plus/activities/MapActivity.java | 4 - .../osmand/plus/helpers/DiscountHelper.java | 2 +- .../plus/inapp/InAppPurchaseHelper.java | 678 +++--------------- .../net/osmand/plus/inapp/InAppPurchases.java | 74 +- OsmAndHms.jks | Bin 0 -> 2243 bytes agconnect-services.json | 1 + 15 files changed, 1264 insertions(+), 737 deletions(-) delete mode 100644 OsmAnd/AndroidManifest-huawei.xml create mode 100644 OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java create mode 100644 OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java delete mode 100644 OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java create mode 100644 OsmAndHms.jks create mode 100644 agconnect-services.json diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index e3071e5fbf..e1887fffff 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -17,6 +17,7 @@ libs/huawei-*.jar huaweidrmlib/ HwDRM_SDK_* drm_strings.xml +agconnect-services.json # copy_widget_icons.sh res/drawable-large/map_* diff --git a/OsmAnd/AndroidManifest-freehuawei.xml b/OsmAnd/AndroidManifest-freehuawei.xml index e96bb7fe47..c234628537 100644 --- a/OsmAnd/AndroidManifest-freehuawei.xml +++ b/OsmAnd/AndroidManifest-freehuawei.xml @@ -2,24 +2,24 @@ - - - - + + + + + - + tools:replace="android:authorities" + android:authorities="net.osmand.huawei.fileprovider"/> - \ No newline at end of file diff --git a/OsmAnd/AndroidManifest-huawei.xml b/OsmAnd/AndroidManifest-huawei.xml deleted file mode 100644 index bc847980cd..0000000000 --- a/OsmAnd/AndroidManifest-huawei.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 91b0b8022f..0aef4aa139 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -40,10 +40,17 @@ android { keyAlias "osmand" keyPassword System.getenv("OSMAND_APK_PASSWORD") } + + publishingHuawei { + storeFile file("/var/lib/jenkins/osmand_hw_key") + storePassword System.getenv("OSMAND_HW_APK_PASSWORD") + keyAlias "OsmAndHms" + keyPassword System.getenv("OSMAND_HW_APK_PASSWORD") + } } defaultConfig { - minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 + minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 17 targetSdkVersion 28 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode @@ -107,19 +114,23 @@ android { debug { manifest.srcFile "AndroidManifest-debug.xml" } + full { + java.srcDirs = ["src-google"] + } free { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-free.xml" } freedev { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-freedev.xml" } freecustom { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-freecustom.xml" } - huawei { - manifest.srcFile "AndroidManifest-huawei.xml" - } freehuawei { + java.srcDirs = ["src-huawei"] manifest.srcFile "AndroidManifest-freehuawei.xml" } @@ -191,10 +202,6 @@ android { resConfig "en" //resConfigs "xxhdpi", "nodpi" } - huawei { - dimension "version" - applicationId "net.osmand.plus.huawei" - } freehuawei { dimension "version" applicationId "net.osmand.huawei" @@ -219,7 +226,10 @@ android { signingConfig signingConfigs.development } release { - signingConfig signingConfigs.publishing + productFlavors.all { flavor -> + flavor.signingConfig signingConfigs.publishing + } + productFlavors.freehuawei.signingConfig signingConfigs.publishingHuawei } } @@ -276,46 +286,14 @@ task downloadWorldMiniBasemap { } } -task downloadHuaweiDrmZip { +task setupHuaweiConfig { doLast { - ant.get(src: 'https://obs.cn-north-2.myhwclouds.com/hms-ds-wf/sdk/HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'HwDRM_SDK_2.5.2.300_ADT.zip', skipexisting: 'true') - ant.unzip(src: 'HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'huaweidrmlib/') + if (System.getenv("HUAWEI_SDK_JSON")) { + new File("agconnect-services.json").text = System.getenv("HUAWEI_SDK_JSON") + } } } -task copyHuaweiDrmLibs(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/libs" - into "libs" -} - -task copyHuaweiDrmValues(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/res" - into "res" -} - -task downloadPrebuiltHuaweiDrm { - dependsOn copyHuaweiDrmLibs, copyHuaweiDrmValues -} - -task cleanHuaweiDrmLibs(type: Delete) { - delete "huaweidrmlib" - delete fileTree("libs").matching { - include "**/huawei-*.jar" - } -} - -task cleanHuaweiDrmValues(type: Delete) { - delete fileTree("res").matching { - include "**/drm_strings.xml" - } -} - -task cleanPrebuiltHuaweiDrm { - dependsOn cleanHuaweiDrmLibs, cleanHuaweiDrmValues -} - task collectVoiceAssets(type: Sync) { from "../../resources/voice" into "assets/voice" @@ -397,8 +375,6 @@ task copyLargePOIIcons(type: Sync) { } } - - task copyWidgetIconsXhdpi(type: Sync) { from "res/drawable-xxhdpi/" into "res/drawable-large-xhdpi/" @@ -445,13 +421,9 @@ task collectExternalResources { copyPoiCategories, downloadWorldMiniBasemap - Gradle gradle = getGradle() - String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() - // Use Drm SDK only for huawei build + String tskReqStr = gradle.startParameter.taskNames.toString() if (tskReqStr.contains("huawei")) { - dependsOn downloadPrebuiltHuaweiDrm - } else { - dependsOn cleanPrebuiltHuaweiDrm + dependsOn setupHuaweiConfig } } @@ -503,10 +475,16 @@ task cleanupDuplicatesInCore() { file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so")) } } + afterEvaluate { android.applicationVariants.all { variant -> variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) } + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() + if (tskReqStr.contains("huawei")) { + apply plugin: 'com.huawei.agconnect' + } } task appStart(type: Exec) { @@ -516,7 +494,6 @@ task appStart(type: Exec) { // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' } - dependencies { implementation project(path: ':OsmAnd-java', configuration: 'android') implementation project(':OsmAnd-api') @@ -565,6 +542,6 @@ dependencies { } implementation 'com.jaredrummler:colorpicker:1.1.0' - huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') - freehuaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') + //freehuaweiImplementation 'com.huawei.agconnect:agconnect-core:1.4.1.300' + freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' } diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java new file mode 100644 index 0000000000..c48294aeb2 --- /dev/null +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -0,0 +1,546 @@ +package net.osmand.plus.inapp; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsResponseListener; + +import net.osmand.AndroidUtils; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.util.BillingManager; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import java.lang.ref.WeakReference; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { + + // The helper object + private BillingManager billingManager; + private List skuDetailsList; + + /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY + * (that you got from the Google Play developer console). This is not your + * developer public key, it's the *app-specific* public key. + * + * Instead of just storing the entire literal string here embedded in the + * program, construct the key at runtime from pieces or + * use bit manipulation (for example, XOR with some other string) to hide + * the actual key. The key itself is not secret information, but we don't + * want to make it easy for an attacker to replace the public key with one + * of their own and then fake messages from the server. + */ + private static final String BASE64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" + + "UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" + + "wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" + + "OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" + + "I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" + + "YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB"; + + public InAppPurchaseHelperImpl(OsmandApplication ctx) { + super(ctx); + } + + private BillingManager getBillingManager() { + return billingManager; + } + + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { + billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { + + @Override + public void onBillingClientSetupFinished() { + logDebug("Setup finished."); + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (!billingManager.isServiceConnected()) { + // Oh noes, there was a problem. + //complain("Problem setting up in-app billing: " + result); + notifyError(taskType, billingManager.getBillingClientResponseMessage()); + stop(true); + return; + } + + processingTask = !runnable.run(InAppPurchaseHelperImpl.this); + } + + @Override + public void onConsumeFinished(String token, BillingResult billingResult) { + } + + @Override + public void onPurchasesUpdated(final List purchases) { + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { + List skuInApps = new ArrayList<>(); + for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + skuInApps.add(purchase.getSku()); + } + for (Purchase p : purchases) { + skuInApps.add(p.getSku()); + } + billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuSubscriptions = new ArrayList<>(); + for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { + skuSubscriptions.add(subscription.getSku()); + } + for (Purchase p : purchases) { + skuSubscriptions.add(p.getSku()); + } + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuDetailsList = new ArrayList<>(skuDetailsListInApps); + skuDetailsList.addAll(skuDetailsListSubscriptions); + InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList; + + mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); + } + }); + } + }); + } + for (Purchase purchase : purchases) { + if (!purchase.isAcknowledged()) { + onPurchaseFinished(purchase); + } + } + } + + @Override + public void onPurchaseCanceled() { + stop(true); + } + }); + } + + public void purchaseFullVersion(final Activity activity) { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); + exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku()); + if (skuDetails == null) { + throw new IllegalArgumentException("Cannot find sku details"); + } + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.initiatePurchaseFlow(activity, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + complain("Cannot launch full version purchase!"); + logError("purchaseFullVersion Error", e); + stop(true); + } + return true; + } + }); + } + + public void purchaseDepthContours(final Activity activity) { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); + exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku()); + if (skuDetails == null) { + throw new IllegalArgumentException("Cannot find sku details"); + } + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.initiatePurchaseFlow(activity, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + complain("Cannot launch depth contours purchase!"); + logError("purchaseDepthContours Error", e); + stop(true); + } + return true; + } + }); + } + + @Nullable + private SkuDetails getSkuDetails(@NonNull String sku) { + List skuDetailsList = this.skuDetailsList; + if (skuDetailsList != null) { + for (SkuDetails details : skuDetailsList) { + if (details.getSku().equals(sku)) { + return details; + } + } + } + return null; + } + + private boolean hasDetails(@NonNull String sku) { + return getSkuDetails(sku) != null; + } + + @Nullable + private Purchase getPurchase(@NonNull String sku) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + List purchases = billingManager.getPurchases(); + if (purchases != null) { + for (Purchase p : purchases) { + if (p.getSku().equals(sku)) { + return p; + } + } + } + } + return null; + } + + // Listener that's called when we finish querying the items and subscriptions we own + private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { + + @NonNull + private List getAllOwnedSubscriptionSkus() { + List result = new ArrayList<>(); + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + for (Purchase p : billingManager.getPurchases()) { + if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { + result.add(p.getSku()); + } + } + } + return result; + } + + @Override + public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { + + logDebug("Query sku details finished."); + + // Have we been disposed of in the meantime? If so, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inventory: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + logDebug("Query sku details was successful."); + + /* + * Check for items we own. Notice that for each purchase, we check + * the developer payload to see if it's correct! See + * verifyDeveloperPayload(). + */ + + List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); + for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + if (hasDetails(s.getSku())) { + Purchase purchase = getPurchase(s.getSku()); + SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); + if (liveUpdatesDetails != null) { + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + allOwnedSubscriptionSkus.remove(s.getSku()); + } + } + for (String sku : allOwnedSubscriptionSkus) { + Purchase purchase = getPurchase(sku); + SkuDetails liveUpdatesDetails = getSkuDetails(sku); + if (liveUpdatesDetails != null) { + InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + if (s == null) { + s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); + } + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + } + + InAppPurchases.InAppPurchase fullVersion = getFullVersion(); + if (hasDetails(fullVersion.getSku())) { + Purchase purchase = getPurchase(fullVersion.getSku()); + SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); + if (fullPriceDetails != null) { + fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); + } + } + + InAppPurchases.InAppPurchase depthContours = getDepthContours(); + if (hasDetails(depthContours.getSku())) { + Purchase purchase = getPurchase(depthContours.getSku()); + SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); + if (depthContoursDetails != null) { + fetchInAppPurchase(depthContours, depthContoursDetails, purchase); + } + } + + InAppPurchases.InAppPurchase contourLines = getContourLines(); + if (hasDetails(contourLines.getSku())) { + Purchase purchase = getPurchase(contourLines.getSku()); + SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); + if (contourLinesDetails != null) { + fetchInAppPurchase(contourLines, contourLinesDetails, purchase); + } + } + + Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); + boolean fullVersionPurchased = fullVersionPurchase != null; + if (fullVersionPurchased) { + ctx.getSettings().FULL_VERSION_PURCHASED.set(true); + } + + Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); + boolean depthContoursPurchased = depthContoursPurchase != null; + if (depthContoursPurchased) { + ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); + } + + // Do we have the live updates? + boolean subscribedToLiveUpdates = false; + List liveUpdatesPurchases = new ArrayList<>(); + for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + Purchase purchase = getPurchase(p.getSku()); + if (purchase != null) { + liveUpdatesPurchases.add(purchase); + if (!subscribedToLiveUpdates) { + subscribedToLiveUpdates = true; + } + } + } + OsmandSettings.OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; + if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { + if (subscriptionCancelledTime.get() == 0) { + subscriptionCancelledTime.set(System.currentTimeMillis()); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); + } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); + if (!isDepthContoursPurchased(ctx)) { + ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); + } + } + } else if (subscribedToLiveUpdates) { + subscriptionCancelledTime.set(0L); + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); + } + + lastValidationCheckTime = System.currentTimeMillis(); + logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + + " live updates purchased."); + + OsmandSettings settings = ctx.getSettings(); + settings.INAPPS_READ.set(true); + + List tokensToSend = new ArrayList<>(); + if (liveUpdatesPurchases.size() > 0) { + List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); + for (Purchase purchase : liveUpdatesPurchases) { + if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) + && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { + String payload = purchase.getDeveloperPayload(); + if (!Algorithms.isEmpty(payload)) { + String[] arr = payload.split(" "); + if (arr.length > 0) { + settings.BILLING_USER_ID.set(arr[0]); + } + if (arr.length > 1) { + token = arr[1]; + settings.BILLING_USER_TOKEN.set(token); + } + } + } + if (!tokensSent.contains(purchase.getSku())) { + tokensToSend.add(purchase); + } + } + } + List purchaseInfoList = new ArrayList<>(); + for (Purchase purchase : tokensToSend) { + purchaseInfoList.add(getPurchaseInfo(purchase)); + } + onSkuDetailsResponseDone(purchaseInfoList); + } + }; + + private PurchaseInfo getPurchaseInfo(Purchase purchase) { + return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); + } + + private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { + if (purchase != null) { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); + } else { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + } + inAppPurchase.setPrice(skuDetails.getPrice()); + inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); + if (skuDetails.getPriceAmountMicros() > 0) { + inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); + } + String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); + if (!Algorithms.isEmpty(subscriptionPeriod)) { + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + try { + ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + } catch (ParseException e) { + LOG.error(e); + } + } + } + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + String introductoryPrice = skuDetails.getIntroductoryPrice(); + String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); + String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); + long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); + if (!Algorithms.isEmpty(introductoryPrice)) { + InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + try { + s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, + introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); + } catch (ParseException e) { + LOG.error(e); + } + } + } + } + + protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + Activity a = activity.get(); + SkuDetails skuDetails = getSkuDetails(sku); + if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.setPayload(payload); + billingManager.initiatePurchaseFlow(a, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } else { + stop(true); + } + } catch (Exception e) { + logError("launchPurchaseFlow Error", e); + stop(true); + } + return true; + } + }; + } + + protected InAppRunnable getRequestInventoryCommand() { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + logDebug("Setup successful. Querying inventory."); + try { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.queryPurchases(); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + logError("queryInventoryAsync Error", e); + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + stop(true); + } + return true; + } + }; + } + + // Call when a purchase is finished + private void onPurchaseFinished(Purchase purchase) { + logDebug("Purchase finished: " + purchase); + + // if we were disposed of in the meantime, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + onPurchaseDone(getPurchaseInfo(purchase)); + } + + @Override + protected boolean isBillingManagerExists() { + return getBillingManager() != null; + } + + @Override + protected void destroyBillingManager() { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.destroy(); + this.billingManager = null; + } + } +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java new file mode 100644 index 0000000000..a7e8589f33 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -0,0 +1,475 @@ +package net.osmand.plus.inapp; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.AndroidUtils; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.util.BillingManager; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import java.lang.ref.WeakReference; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { + + // The helper object + private BillingManager billingManager; + private List skuDetailsList; + + + public InAppPurchaseHelperImpl(OsmandApplication ctx) { + super(ctx); + } + + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { + billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { + + @Override + public void onBillingClientSetupFinished() { + logDebug("Setup finished."); + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (!billingManager.isServiceConnected()) { + // Oh noes, there was a problem. + //complain("Problem setting up in-app billing: " + result); + notifyError(taskType, billingManager.getBillingClientResponseMessage()); + stop(true); + return; + } + + processingTask = !runnable.run(InAppPurchaseHelperImpl.this); + } + + @Override + public void onConsumeFinished(String token, BillingResult billingResult) { + } + + @Override + public void onPurchasesUpdated(final List purchases) { + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { + List skuInApps = new ArrayList<>(); + for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + skuInApps.add(purchase.getSku()); + } + for (Purchase p : purchases) { + skuInApps.add(p.getSku()); + } + billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuSubscriptions = new ArrayList<>(); + for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { + skuSubscriptions.add(subscription.getSku()); + } + for (Purchase p : purchases) { + skuSubscriptions.add(p.getSku()); + } + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuDetailsList = new ArrayList<>(skuDetailsListInApps); + skuDetailsList.addAll(skuDetailsListSubscriptions); + InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList; + + mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); + } + }); + } + }); + } + for (Purchase purchase : purchases) { + if (!purchase.isAcknowledged()) { + onPurchaseFinished(purchase); + } + } + } + + @Override + public void onPurchaseCanceled() { + stop(true); + } + }); + } + + @Override + public void purchaseFullVersion(Activity activity) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public void purchaseDepthContours(Activity activity) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Nullable + private SkuDetails getSkuDetails(@NonNull String sku) { + List skuDetailsList = this.skuDetailsList; + if (skuDetailsList != null) { + for (SkuDetails details : skuDetailsList) { + if (details.getSku().equals(sku)) { + return details; + } + } + } + return null; + } + + private boolean hasDetails(@NonNull String sku) { + return getSkuDetails(sku) != null; + } + + @Nullable + private Purchase getPurchase(@NonNull String sku) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + List purchases = billingManager.getPurchases(); + if (purchases != null) { + for (Purchase p : purchases) { + if (p.getSku().equals(sku)) { + return p; + } + } + } + } + return null; + } + + // Listener that's called when we finish querying the items and subscriptions we own + private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { + + @NonNull + private List getAllOwnedSubscriptionSkus() { + List result = new ArrayList<>(); + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + for (Purchase p : billingManager.getPurchases()) { + if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { + result.add(p.getSku()); + } + } + } + return result; + } + + @Override + public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { + + logDebug("Query sku details finished."); + + // Have we been disposed of in the meantime? If so, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inventory: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + logDebug("Query sku details was successful."); + + /* + * Check for items we own. Notice that for each purchase, we check + * the developer payload to see if it's correct! See + * verifyDeveloperPayload(). + */ + + List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); + for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + if (hasDetails(s.getSku())) { + Purchase purchase = getPurchase(s.getSku()); + SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); + if (liveUpdatesDetails != null) { + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + allOwnedSubscriptionSkus.remove(s.getSku()); + } + } + for (String sku : allOwnedSubscriptionSkus) { + Purchase purchase = getPurchase(sku); + SkuDetails liveUpdatesDetails = getSkuDetails(sku); + if (liveUpdatesDetails != null) { + InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + if (s == null) { + s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); + } + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + } + + InAppPurchases.InAppPurchase fullVersion = getFullVersion(); + if (hasDetails(fullVersion.getSku())) { + Purchase purchase = getPurchase(fullVersion.getSku()); + SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); + if (fullPriceDetails != null) { + fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); + } + } + + InAppPurchases.InAppPurchase depthContours = getDepthContours(); + if (hasDetails(depthContours.getSku())) { + Purchase purchase = getPurchase(depthContours.getSku()); + SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); + if (depthContoursDetails != null) { + fetchInAppPurchase(depthContours, depthContoursDetails, purchase); + } + } + + InAppPurchases.InAppPurchase contourLines = getContourLines(); + if (hasDetails(contourLines.getSku())) { + Purchase purchase = getPurchase(contourLines.getSku()); + SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); + if (contourLinesDetails != null) { + fetchInAppPurchase(contourLines, contourLinesDetails, purchase); + } + } + + Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); + boolean fullVersionPurchased = fullVersionPurchase != null; + if (fullVersionPurchased) { + ctx.getSettings().FULL_VERSION_PURCHASED.set(true); + } + + Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); + boolean depthContoursPurchased = depthContoursPurchase != null; + if (depthContoursPurchased) { + ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); + } + + // Do we have the live updates? + boolean subscribedToLiveUpdates = false; + List liveUpdatesPurchases = new ArrayList<>(); + for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + Purchase purchase = getPurchase(p.getSku()); + if (purchase != null) { + liveUpdatesPurchases.add(purchase); + if (!subscribedToLiveUpdates) { + subscribedToLiveUpdates = true; + } + } + } + OsmandSettings.OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; + if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { + if (subscriptionCancelledTime.get() == 0) { + subscriptionCancelledTime.set(System.currentTimeMillis()); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); + } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); + if (!isDepthContoursPurchased(ctx)) { + ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); + } + } + } else if (subscribedToLiveUpdates) { + subscriptionCancelledTime.set(0L); + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); + } + + lastValidationCheckTime = System.currentTimeMillis(); + logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + + " live updates purchased."); + + OsmandSettings settings = ctx.getSettings(); + settings.INAPPS_READ.set(true); + + List tokensToSend = new ArrayList<>(); + if (liveUpdatesPurchases.size() > 0) { + List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); + for (Purchase purchase : liveUpdatesPurchases) { + if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) + && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { + String payload = purchase.getDeveloperPayload(); + if (!Algorithms.isEmpty(payload)) { + String[] arr = payload.split(" "); + if (arr.length > 0) { + settings.BILLING_USER_ID.set(arr[0]); + } + if (arr.length > 1) { + token = arr[1]; + settings.BILLING_USER_TOKEN.set(token); + } + } + } + if (!tokensSent.contains(purchase.getSku())) { + tokensToSend.add(purchase); + } + } + } + List purchaseInfoList = new ArrayList<>(); + for (Purchase purchase : tokensToSend) { + purchaseInfoList.add(getPurchaseInfo(purchase)); + } + onSkuDetailsResponseDone(purchaseInfoList); + } + }; + + private PurchaseInfo getPurchaseInfo(Purchase purchase) { + return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); + } + + private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { + if (purchase != null) { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); + } else { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + } + inAppPurchase.setPrice(skuDetails.getPrice()); + inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); + if (skuDetails.getPriceAmountMicros() > 0) { + inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); + } + String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); + if (!Algorithms.isEmpty(subscriptionPeriod)) { + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + try { + ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + } catch (ParseException e) { + LOG.error(e); + } + } + } + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + String introductoryPrice = skuDetails.getIntroductoryPrice(); + String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); + String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); + long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); + if (!Algorithms.isEmpty(introductoryPrice)) { + InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + try { + s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, + introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); + } catch (ParseException e) { + LOG.error(e); + } + } + } + } + + protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + Activity a = activity.get(); + SkuDetails skuDetails = getSkuDetails(sku); + if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.setPayload(payload); + billingManager.initiatePurchaseFlow(a, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } else { + stop(true); + } + } catch (Exception e) { + logError("launchPurchaseFlow Error", e); + stop(true); + } + return true; + } + }; + } + + protected InAppRunnable getRequestInventoryCommand() { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + logDebug("Setup successful. Querying inventory."); + try { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.queryPurchases(); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + logError("queryInventoryAsync Error", e); + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + stop(true); + } + return true; + } + }; + } + + // Call when a purchase is finished + private void onPurchaseFinished(Purchase purchase) { + logDebug("Purchase finished: " + purchase); + + // if we were disposed of in the meantime, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + onPurchaseDone(getPurchaseInfo(purchase)); + } + + @Override + protected boolean isBillingManagerExists() { + return getBillingManager() != null; + } + + @Override + protected void destroyBillingManager() { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.destroy(); + this.billingManager = null; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 2837bff6f8..0ae24b7c5f 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -38,7 +38,7 @@ import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask; import net.osmand.plus.helpers.AvoidSpecificRoads; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.WaypointHelper; -import net.osmand.plus.inapp.InAppPurchaseHelper; +import net.osmand.plus.inapp.InAppPurchaseHelperImpl; import net.osmand.plus.liveupdates.LiveUpdatesHelper; import net.osmand.plus.mapmarkers.MapMarkersDbHelper; import net.osmand.plus.monitoring.LiveMonitoringHelper; @@ -428,7 +428,7 @@ public class AppInitializer implements IProgress { } getLazyRoutingConfig(); app.applyTheme(app); - app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelper(app), InAppPurchaseHelper.class); + app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelperImpl(app), InAppPurchaseHelperImpl.class); app.poiTypes = startupInit(MapPoiTypes.getDefaultNoInit(), MapPoiTypes.class); app.transportRoutingHelper = startupInit(new TransportRoutingHelper(app), TransportRoutingHelper.class); app.routingHelper = startupInit(new RoutingHelper(app), RoutingHelper.class); diff --git a/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java b/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java deleted file mode 100644 index 7cc2f2798e..0000000000 --- a/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.osmand.plus; - -import android.app.Activity; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class HuaweiDrmHelper { - private static final String TAG = HuaweiDrmHelper.class.getSimpleName(); - - //Copyright protection id - private static final String DRM_ID = "101117397"; - //Copyright protection public key - private static final String DRM_PUBLIC_KEY = "9d6f861e7d46be167809a6a62302749a6753b3c1bd02c9729efb3973e268091d"; - - public static void check(Activity activity) { - boolean succeed = false; - try { - final WeakReference activityRef = new WeakReference<>(activity); - Class drmCheckCallbackClass = Class.forName("com.huawei.android.sdk.drm.DrmCheckCallback"); - Object callback = java.lang.reflect.Proxy.newProxyInstance( - drmCheckCallbackClass.getClassLoader(), - new java.lang.Class[]{drmCheckCallbackClass}, - new java.lang.reflect.InvocationHandler() { - - @Override - public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) { - Activity a = activityRef.get(); - if (a != null && !a.isFinishing()) { - String method_name = method.getName(); - if (method_name.equals("onCheckSuccess")) { - // skip now - } else if (method_name.equals("onCheckFailed")) { - closeApplication(a); - } - } - return null; - } - }); - - Class drmClass = Class.forName("com.huawei.android.sdk.drm.Drm"); - Class[] partypes = new Class[]{Activity.class, String.class, String.class, String.class, drmCheckCallbackClass}; - Method check = drmClass.getMethod("check", partypes); - check.invoke(null, activity, activity.getPackageName(), DRM_ID, DRM_PUBLIC_KEY, callback); - succeed = true; - - } catch (ClassNotFoundException e) { - Log.e(TAG, "check: ", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "check: ", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "check: ", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "check: ", e); - } - if (!succeed) { - closeApplication(activity); - } - } - - private static void closeApplication(Activity activity) { - ((OsmandApplication) activity.getApplication()).closeApplicationAnywayImpl(activity, true); - } -} diff --git a/OsmAnd/src/net/osmand/plus/Version.java b/OsmAnd/src/net/osmand/plus/Version.java index 14ed68b100..86d26ec954 100644 --- a/OsmAnd/src/net/osmand/plus/Version.java +++ b/OsmAnd/src/net/osmand/plus/Version.java @@ -121,8 +121,8 @@ public class Version { public static boolean isFreeVersion(OsmandApplication ctx){ return ctx.getPackageName().equals(FREE_VERSION_NAME) || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME) || - ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) - ; + ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) || + isHuawei(ctx); } public static boolean isPaidVersion(OsmandApplication ctx) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index e23e596dcf..db9f055406 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -67,7 +67,6 @@ import net.osmand.plus.AppInitializer; import net.osmand.plus.AppInitializer.AppInitializeListener; import net.osmand.plus.AppInitializer.InitEvents; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; -import net.osmand.plus.HuaweiDrmHelper; import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.OnDismissDialogFragmentListener; @@ -276,9 +275,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven super.onCreate(savedInstanceState); - if (Version.isHuawei(getMyApplication())) { - HuaweiDrmHelper.check(this); - } // Full screen is not used here // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); diff --git a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java index 61ae82399f..855f648332 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java @@ -81,7 +81,7 @@ public class DiscountHelper { public static void checkAndDisplay(final MapActivity mapActivity) { OsmandApplication app = mapActivity.getMyApplication(); OsmandSettings settings = app.getSettings(); - if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get() || Version.isHuawei(app)) { + if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get()) { return; } if (mBannerVisible) { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index ac7e2c77fb..c037c13d0a 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -9,33 +9,21 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.billingclient.api.BillingClient.BillingResponseCode; -import com.android.billingclient.api.BillingClient.SkuType; -import com.android.billingclient.api.BillingResult; -import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.SkuDetails; -import com.android.billingclient.api.SkuDetailsResponseListener; - import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils.OnRequestResultListener; import net.osmand.AndroidNetworkUtils.OnRequestsResultListener; import net.osmand.AndroidNetworkUtils.RequestResponse; import net.osmand.PlatformUtil; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference; import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState; -import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; -import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList; -import net.osmand.plus.inapp.util.BillingManager; -import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener; import net.osmand.plus.liveupdates.CountrySelectionFragment; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.Algorithms; import org.json.JSONArray; @@ -43,7 +31,6 @@ import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -53,52 +40,28 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class InAppPurchaseHelper { +public abstract class InAppPurchaseHelper { // Debug tag, for logging - private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class); + protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class); private static final String TAG = InAppPurchaseHelper.class.getSimpleName(); private boolean mDebugLog = false; public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days - private InAppPurchases purchases; - private long lastValidationCheckTime; - private boolean inventoryRequested; + protected InAppPurchases purchases; + protected long lastValidationCheckTime; + protected boolean inventoryRequested; private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily - // (arbitrary) request code for the purchase flow - private static final int RC_REQUEST = 10001; - // The helper object - private BillingManager billingManager; - private List skuDetailsList; + protected boolean isDeveloperVersion; + protected String token = ""; + protected InAppPurchaseTaskType activeTask; + protected boolean processingTask = false; + protected boolean inventoryRequestPending = false; - private boolean isDeveloperVersion; - private String token = ""; - private InAppPurchaseTaskType activeTask; - private boolean processingTask = false; - private boolean inventoryRequestPending = false; - - private OsmandApplication ctx; - private InAppPurchaseListener uiActivity = null; - - /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY - * (that you got from the Google Play developer console). This is not your - * developer public key, it's the *app-specific* public key. - * - * Instead of just storing the entire literal string here embedded in the - * program, construct the key at runtime from pieces or - * use bit manipulation (for example, XOR with some other string) to hide - * the actual key. The key itself is not secret information, but we don't - * want to make it easy for an attacker to replace the public key with one - * of their own and then fake messages from the server. - */ - private static final String BASE64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" + - "UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" + - "wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" + - "OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" + - "I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" + - "YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB"; + protected OsmandApplication ctx; + protected InAppPurchaseListener uiActivity = null; public interface InAppPurchaseListener { void onError(InAppPurchaseTaskType taskType, String error); @@ -124,6 +87,30 @@ public class InAppPurchaseHelper { boolean run(InAppPurchaseHelper helper); } + public static class PurchaseInfo { + private String sku; + private String orderId; + private String purchaseToken; + + public PurchaseInfo(String sku, String orderId, String purchaseToken) { + this.sku = sku; + this.orderId = orderId; + this.purchaseToken = purchaseToken; + } + + public String getSku() { + return sku; + } + + public String getOrderId() { + return orderId; + } + + public String getPurchaseToken() { + return purchaseToken; + } + } + public String getToken() { return token; } @@ -194,11 +181,7 @@ public class InAppPurchaseHelper { return false; } - private BillingManager getBillingManager() { - return billingManager; - } - - private void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { + protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) { notifyDismissProgress(taskType); stop(true); @@ -222,117 +205,15 @@ public class InAppPurchaseHelper { try { processingTask = true; activeTask = taskType; - billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingUpdatesListener() { - - @Override - public void onBillingClientSetupFinished() { - logDebug("Setup finished."); - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - if (!billingManager.isServiceConnected()) { - // Oh noes, there was a problem. - //complain("Problem setting up in-app billing: " + result); - notifyError(taskType, billingManager.getBillingClientResponseMessage()); - stop(true); - return; - } - - processingTask = !runnable.run(InAppPurchaseHelper.this); - } - - @Override - public void onConsumeFinished(String token, BillingResult billingResult) { - } - - @Override - public void onPurchasesUpdated(final List purchases) { - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { - List skuInApps = new ArrayList<>(); - for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { - skuInApps.add(purchase.getSku()); - } - for (Purchase p : purchases) { - skuInApps.add(p.getSku()); - } - billingManager.querySkuDetailsAsync(SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - List skuSubscriptions = new ArrayList<>(); - for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { - skuSubscriptions.add(subscription.getSku()); - } - for (Purchase p : purchases) { - skuSubscriptions.add(p.getSku()); - } - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - billingManager.querySkuDetailsAsync(SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - List skuDetailsList = new ArrayList<>(skuDetailsListInApps); - skuDetailsList.addAll(skuDetailsListSubscriptions); - InAppPurchaseHelper.this.skuDetailsList = skuDetailsList; - - mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); - } - }); - } - }); - } - for (Purchase purchase : purchases) { - if (!purchase.isAcknowledged()) { - onPurchaseFinished(purchase); - } - } - } - - @Override - public void onPurchaseCanceled() { - stop(true); - } - }); + execImpl(taskType, runnable); } catch (Exception e) { logError("exec Error", e); stop(true); } } + protected abstract void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable); + public boolean needRequestInventory() { return !inventoryRequested && ((isSubscribedToLiveUpdates(ctx) && Algorithms.isEmpty(ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get())) || System.currentTimeMillis() - lastValidationCheckTime > PURCHASE_VALIDATION_PERIOD_MSEC); @@ -343,32 +224,7 @@ public class InAppPurchaseHelper { new RequestInventoryTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public void purchaseFullVersion(final Activity activity) { - notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); - exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku()); - if (skuDetails == null) { - throw new IllegalArgumentException("Cannot find sku details"); - } - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.initiatePurchaseFlow(activity, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - complain("Cannot launch full version purchase!"); - logError("purchaseFullVersion Error", e); - stop(true); - } - return true; - } - }); - } + public abstract void purchaseFullVersion(final Activity activity) throws UnsupportedOperationException; public void purchaseLiveUpdates(Activity activity, String sku, String email, String userName, String countryDownloadName, boolean hideUserName) { @@ -377,288 +233,7 @@ public class InAppPurchaseHelper { .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public void purchaseDepthContours(final Activity activity) { - notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); - exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku()); - if (skuDetails == null) { - throw new IllegalArgumentException("Cannot find sku details"); - } - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.initiatePurchaseFlow(activity, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - complain("Cannot launch depth contours purchase!"); - logError("purchaseDepthContours Error", e); - stop(true); - } - return true; - } - }); - } - - @Nullable - private SkuDetails getSkuDetails(@NonNull String sku) { - List skuDetailsList = this.skuDetailsList; - if (skuDetailsList != null) { - for (SkuDetails details : skuDetailsList) { - if (details.getSku().equals(sku)) { - return details; - } - } - } - return null; - } - - private boolean hasDetails(@NonNull String sku) { - return getSkuDetails(sku) != null; - } - - @Nullable - private Purchase getPurchase(@NonNull String sku) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - List purchases = billingManager.getPurchases(); - if (purchases != null) { - for (Purchase p : purchases) { - if (p.getSku().equals(sku)) { - return p; - } - } - } - } - return null; - } - - // Listener that's called when we finish querying the items and subscriptions we own - private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { - - @NonNull - private List getAllOwnedSubscriptionSkus() { - List result = new ArrayList<>(); - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - for (Purchase p : billingManager.getPurchases()) { - if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { - result.add(p.getSku()); - } - } - } - return result; - } - - @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { - - logDebug("Query sku details finished."); - - // Have we been disposed of in the meantime? If so, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query inventory: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - logDebug("Query sku details was successful."); - - /* - * Check for items we own. Notice that for each purchase, we check - * the developer payload to see if it's correct! See - * verifyDeveloperPayload(). - */ - - List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); - for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { - if (hasDetails(s.getSku())) { - Purchase purchase = getPurchase(s.getSku()); - SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); - if (liveUpdatesDetails != null) { - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - allOwnedSubscriptionSkus.remove(s.getSku()); - } - } - for (String sku : allOwnedSubscriptionSkus) { - Purchase purchase = getPurchase(sku); - SkuDetails liveUpdatesDetails = getSkuDetails(sku); - if (liveUpdatesDetails != null) { - InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); - if (s == null) { - s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); - } - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - } - - InAppPurchase fullVersion = getFullVersion(); - if (hasDetails(fullVersion.getSku())) { - Purchase purchase = getPurchase(fullVersion.getSku()); - SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); - if (fullPriceDetails != null) { - fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); - } - } - - InAppPurchase depthContours = getDepthContours(); - if (hasDetails(depthContours.getSku())) { - Purchase purchase = getPurchase(depthContours.getSku()); - SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); - if (depthContoursDetails != null) { - fetchInAppPurchase(depthContours, depthContoursDetails, purchase); - } - } - - InAppPurchase contourLines = getContourLines(); - if (hasDetails(contourLines.getSku())) { - Purchase purchase = getPurchase(contourLines.getSku()); - SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); - if (contourLinesDetails != null) { - fetchInAppPurchase(contourLines, contourLinesDetails, purchase); - } - } - - Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); - boolean fullVersionPurchased = fullVersionPurchase != null; - if (fullVersionPurchased) { - ctx.getSettings().FULL_VERSION_PURCHASED.set(true); - } - - Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); - boolean depthContoursPurchased = depthContoursPurchase != null; - if (depthContoursPurchased) { - ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); - } - - // Do we have the live updates? - boolean subscribedToLiveUpdates = false; - List liveUpdatesPurchases = new ArrayList<>(); - for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { - Purchase purchase = getPurchase(p.getSku()); - if (purchase != null) { - liveUpdatesPurchases.add(purchase); - if (!subscribedToLiveUpdates) { - subscribedToLiveUpdates = true; - } - } - } - OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; - if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { - if (subscriptionCancelledTime.get() == 0) { - subscriptionCancelledTime.set(System.currentTimeMillis()); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); - } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); - if (!isDepthContoursPurchased(ctx)) { - ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); - } - } - } else if (subscribedToLiveUpdates) { - subscriptionCancelledTime.set(0L); - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); - } - - lastValidationCheckTime = System.currentTimeMillis(); - logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") - + " live updates purchased."); - - OsmandSettings settings = ctx.getSettings(); - settings.INAPPS_READ.set(true); - - List tokensToSend = new ArrayList<>(); - if (liveUpdatesPurchases.size() > 0) { - List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); - for (Purchase purchase : liveUpdatesPurchases) { - if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) - && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { - String payload = purchase.getDeveloperPayload(); - if (!Algorithms.isEmpty(payload)) { - String[] arr = payload.split(" "); - if (arr.length > 0) { - settings.BILLING_USER_ID.set(arr[0]); - } - if (arr.length > 1) { - token = arr[1]; - settings.BILLING_USER_TOKEN.set(token); - } - } - } - if (!tokensSent.contains(purchase.getSku())) { - tokensToSend.add(purchase); - } - } - } - - final OnRequestResultListener listener = new OnRequestResultListener() { - @Override - public void onResult(String result) { - notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); - notifyGetItems(); - stop(true); - logDebug("Initial inapp query finished"); - } - }; - - if (tokensToSend.size() > 0) { - sendTokens(tokensToSend, listener); - } else { - listener.onResult("OK"); - } - } - }; - - private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { - if (purchase != null) { - inAppPurchase.setPurchaseState(PurchaseState.PURCHASED); - inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); - } else { - inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED); - } - inAppPurchase.setPrice(skuDetails.getPrice()); - inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); - if (skuDetails.getPriceAmountMicros() > 0) { - inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); - } - String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); - if (!Algorithms.isEmpty(subscriptionPeriod)) { - if (inAppPurchase instanceof InAppSubscription) { - try { - ((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); - } catch (ParseException e) { - LOG.error(e); - } - } - } - if (inAppPurchase instanceof InAppSubscription) { - String introductoryPrice = skuDetails.getIntroductoryPrice(); - String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); - String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); - long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); - if (!Algorithms.isEmpty(introductoryPrice)) { - InAppSubscription s = (InAppSubscription) inAppPurchase; - try { - s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice, - introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); - } catch (ParseException e) { - LOG.error(e); - } - } - } - } + public abstract void purchaseDepthContours(final Activity activity) throws UnsupportedOperationException; @SuppressLint("StaticFieldLeak") private class LiveUpdatesPurchaseTask extends AsyncTask { @@ -746,31 +321,7 @@ public class InAppPurchaseHelper { if (!Algorithms.isEmpty(userId) && !Algorithms.isEmpty(token)) { logDebug("Launching purchase flow for live updates subscription for userId=" + userId); final String payload = userId + " " + token; - exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - Activity a = activity.get(); - SkuDetails skuDetails = getSkuDetails(sku); - if (a != null && skuDetails != null) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.setPayload(payload); - billingManager.initiatePurchaseFlow(a, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } else { - stop(true); - } - } catch (Exception e) { - logError("launchPurchaseFlow Error", e); - stop(true); - } - return true; - } - }); + exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, getPurchaseLiveUpdatesCommand(activity, sku, payload)); } else { notifyError(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, "Empty userId"); stop(true); @@ -778,6 +329,9 @@ public class InAppPurchaseHelper { } } + protected abstract InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, + final String sku, final String payload) throws UnsupportedOperationException; + @SuppressLint("StaticFieldLeak") private class RequestInventoryTask extends AsyncTask { @@ -808,38 +362,41 @@ public class InAppPurchaseHelper { try { JSONObject obj = new JSONObject(response); JSONArray names = obj.names(); - for (int i = 0; i < names.length(); i++) { - String skuType = names.getString(i); - JSONObject subObj = obj.getJSONObject(skuType); - String sku = subObj.getString("sku"); - if (!Algorithms.isEmpty(sku)) { - getLiveUpdates().upgradeSubscription(sku); + if (names != null) { + for (int i = 0; i < names.length(); i++) { + String skuType = names.getString(i); + JSONObject subObj = obj.getJSONObject(skuType); + String sku = subObj.getString("sku"); + if (!Algorithms.isEmpty(sku)) { + getLiveUpdates().upgradeSubscription(sku); + } } } } catch (JSONException e) { logError("Json parsing error", e); } } - exec(InAppPurchaseTaskType.REQUEST_INVENTORY, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - logDebug("Setup successful. Querying inventory."); - try { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.queryPurchases(); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - logError("queryInventoryAsync Error", e); - notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); - stop(true); - } - return true; - } - }); + exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand()); + } + } + + protected abstract InAppRunnable getRequestInventoryCommand() throws UnsupportedOperationException; + + protected void onSkuDetailsResponseDone(List purchaseInfoList) { + final AndroidNetworkUtils.OnRequestResultListener listener = new AndroidNetworkUtils.OnRequestResultListener() { + @Override + public void onResult(String result) { + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + notifyGetItems(); + stop(true); + logDebug("Initial inapp query finished"); + } + }; + + if (purchaseInfoList.size() > 0) { + sendTokens(purchaseInfoList, listener); + } else { + listener.onResult("OK"); } } @@ -852,25 +409,16 @@ public class InAppPurchaseHelper { parameters.put("aid", ctx.getUserAndroidId()); } - // Call when a purchase is finished - private void onPurchaseFinished(Purchase purchase) { - logDebug("Purchase finished: " + purchase); - - // if we were disposed of in the meantime, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - + protected void onPurchaseDone(PurchaseInfo info) { logDebug("Purchase successful."); - InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku()); + InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(info.getSku()); if (liveUpdatesPurchase != null) { // bought live updates logDebug("Live updates subscription purchased."); final String sku = liveUpdatesPurchase.getSku(); liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED); - sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() { + sendTokens(Collections.singletonList(info), new OnRequestResultListener() { @Override public void onResult(String result) { boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get(); @@ -887,7 +435,7 @@ public class InAppPurchaseHelper { } }); - } else if (purchase.getSku().equals(getFullVersion().getSku())) { + } else if (info.getSku().equals(getFullVersion().getSku())) { // bought full version getFullVersion().setPurchaseState(PurchaseState.PURCHASED); logDebug("Full version purchased."); @@ -898,7 +446,7 @@ public class InAppPurchaseHelper { notifyItemPurchased(getFullVersion().getSku(), false); stop(true); - } else if (purchase.getSku().equals(getDepthContours().getSku())) { + } else if (info.getSku().equals(getDepthContours().getSku())) { // bought sea depth contours getDepthContours().setPurchaseState(PurchaseState.PURCHASED); logDebug("Sea depth contours purchased."); @@ -921,17 +469,19 @@ public class InAppPurchaseHelper { stop(false); } - private void stop(boolean taskDone) { + protected abstract boolean isBillingManagerExists(); + + protected abstract void destroyBillingManager(); + + protected void stop(boolean taskDone) { logDebug("Destroying helper."); - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { + if (isBillingManagerExists()) { if (taskDone) { processingTask = false; } if (!processingTask) { activeTask = null; - billingManager.destroy(); - this.billingManager = null; + destroyBillingManager(); } } else { processingTask = false; @@ -943,7 +493,7 @@ public class InAppPurchaseHelper { } } - private void sendTokens(@NonNull final List purchases, final OnRequestResultListener listener) { + protected void sendTokens(@NonNull final List purchaseInfoList, final OnRequestResultListener listener) { final String userId = ctx.getSettings().BILLING_USER_ID.get(); final String token = ctx.getSettings().BILLING_USER_TOKEN.get(); final String email = ctx.getSettings().BILLING_USER_EMAIL.get(); @@ -951,12 +501,12 @@ public class InAppPurchaseHelper { String url = "https://osmand.net/subscription/purchased"; String userOperation = "Sending purchase info..."; final List requests = new ArrayList<>(); - for (Purchase purchase : purchases) { + for (PurchaseInfo info : purchaseInfoList) { Map parameters = new HashMap<>(); parameters.put("userid", userId); - parameters.put("sku", purchase.getSku()); - parameters.put("orderId", purchase.getOrderId()); - parameters.put("purchaseToken", purchase.getPurchaseToken()); + parameters.put("sku", info.getSku()); + parameters.put("orderId", info.getOrderId()); + parameters.put("purchaseToken", info.getPurchaseToken()); parameters.put("email", email); parameters.put("token", token); addUserInfo(parameters); @@ -967,9 +517,9 @@ public class InAppPurchaseHelper { public void onResult(@NonNull List results) { for (RequestResponse rr : results) { String sku = rr.getRequest().getParameters().get("sku"); - Purchase purchase = getPurchase(sku); - if (purchase != null) { - updateSentTokens(purchase); + PurchaseInfo info = getPurchaseInfo(sku); + if (info != null) { + updateSentTokens(info); String result = rr.getResponse(); if (result != null) { try { @@ -979,13 +529,13 @@ public class InAppPurchaseHelper { } else { complain("SendToken Error: " + obj.getString("error") - + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")"); + + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")"); } } catch (JSONException e) { logError("SendToken", e); complain("SendToken Error: " + (e.getMessage() != null ? e.getMessage() : "JSONException") - + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")"); + + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")"); } } } @@ -995,10 +545,10 @@ public class InAppPurchaseHelper { } } - private void updateSentTokens(@NonNull Purchase purchase) { + private void updateSentTokens(@NonNull PurchaseInfo info) { String tokensSentStr = ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get(); Set tokensSent = new HashSet<>(Arrays.asList(tokensSentStr.split(";"))); - tokensSent.add(purchase.getSku()); + tokensSent.add(info.getSku()); ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.set(TextUtils.join(";", tokensSent)); } @@ -1032,10 +582,10 @@ public class InAppPurchaseHelper { } @Nullable - private Purchase getPurchase(String sku) { - for (Purchase purchase : purchases) { - if (purchase.getSku().equals(sku)) { - return purchase; + private PurchaseInfo getPurchaseInfo(String sku) { + for (PurchaseInfo info : purchaseInfoList) { + if (info.getSku().equals(sku)) { + return info; } } return null; @@ -1049,31 +599,31 @@ public class InAppPurchaseHelper { } } - private void notifyError(InAppPurchaseTaskType taskType, String message) { + protected void notifyError(InAppPurchaseTaskType taskType, String message) { if (uiActivity != null) { uiActivity.onError(taskType, message); } } - private void notifyGetItems() { + protected void notifyGetItems() { if (uiActivity != null) { uiActivity.onGetItems(); } } - private void notifyItemPurchased(String sku, boolean active) { + protected void notifyItemPurchased(String sku, boolean active) { if (uiActivity != null) { uiActivity.onItemPurchased(sku, active); } } - private void notifyShowProgress(InAppPurchaseTaskType taskType) { + protected void notifyShowProgress(InAppPurchaseTaskType taskType) { if (uiActivity != null) { uiActivity.showProgress(taskType); } } - private void notifyDismissProgress(InAppPurchaseTaskType taskType) { + protected void notifyDismissProgress(InAppPurchaseTaskType taskType) { if (uiActivity != null) { uiActivity.dismissProgress(taskType); } @@ -1090,26 +640,26 @@ public class InAppPurchaseHelper { } } - private void complain(String message) { + protected void complain(String message) { logError("**** InAppPurchaseHelper Error: " + message); showToast(message); } - private void showToast(final String message) { + protected void showToast(final String message) { ctx.showToastMessage(message); } - private void logDebug(String msg) { + protected void logDebug(String msg) { if (mDebugLog) { Log.d(TAG, msg); } } - private void logError(String msg) { + protected void logError(String msg) { Log.e(TAG, msg); } - private void logError(String msg, Throwable e) { + protected void logError(String msg, Throwable e) { Log.e(TAG, "Error: " + msg, e); } diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java index b42b57f045..8c42448ec7 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java @@ -55,6 +55,12 @@ public class InAppPurchases { new InAppPurchaseLiveUpdatesAnnualFree() }; + private static final InAppSubscription[] LIVE_UPDATES_HW_FREE = new InAppSubscription[]{ + new InAppPurchaseLiveUpdatesMonthlyHWFree(), + new InAppPurchaseLiveUpdates3MonthsHWFree(), + new InAppPurchaseLiveUpdatesAnnualHWFree() + }; + private InAppPurchase fullVersion; private InAppPurchase depthContours; private InAppPurchase contourLines; @@ -65,7 +71,9 @@ public class InAppPurchases { InAppPurchases(OsmandApplication ctx) { fullVersion = FULL_VERSION; - if (Version.isFreeVersion(ctx)) { + if (Version.isHuawei(ctx)) { + liveUpdates = new LiveUpdatesInAppPurchasesHWFree(); + } else if (Version.isFreeVersion(ctx)) { liveUpdates = new LiveUpdatesInAppPurchasesFree(); } else { liveUpdates = new LiveUpdatesInAppPurchasesFull(); @@ -267,6 +275,13 @@ public class InAppPurchases { } } + public static class LiveUpdatesInAppPurchasesHWFree extends InAppSubscriptionList { + + public LiveUpdatesInAppPurchasesHWFree() { + super(LIVE_UPDATES_HW_FREE); + } + } + public static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList { public LiveUpdatesInAppPurchasesFull() { @@ -943,6 +958,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdatesMonthlyHWFree extends InAppPurchaseLiveUpdatesMonthly { + + private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.test.monthly"; + + InAppPurchaseLiveUpdatesMonthlyHWFree() { + super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesMonthlyHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyHWFree(sku) : null; + } + } + public static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription { InAppPurchaseLiveUpdates3Months(String skuNoVersion, int version) { @@ -1024,6 +1058,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdates3MonthsHWFree extends InAppPurchaseLiveUpdates3Months { + + private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.test.3months"; + + InAppPurchaseLiveUpdates3MonthsHWFree() { + super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdates3MonthsHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsHWFree(sku) : null; + } + } + public static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription { InAppPurchaseLiveUpdatesAnnual(String skuNoVersion, int version) { @@ -1105,6 +1158,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdatesAnnualHWFree extends InAppPurchaseLiveUpdatesAnnual { + + private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.test.annual"; + + InAppPurchaseLiveUpdatesAnnualHWFree() { + super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesAnnualHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualHWFree(sku) : null; + } + } + public static class InAppPurchaseLiveUpdatesOldMonthly extends InAppPurchaseLiveUpdatesMonthly { InAppPurchaseLiveUpdatesOldMonthly(String sku) { diff --git a/OsmAndHms.jks b/OsmAndHms.jks new file mode 100644 index 0000000000000000000000000000000000000000..b1d7e59362b69659660ae0e1bc29bfdca0f751f2 GIT binary patch literal 2243 zcmchY={Fk)7sj*LnvmF2YF|oi6I+y^YN?2=T1)L)f)cxFETt7KHI!EE65FImETyWn zYS%@Jh*lJ>poUVk7&U6}X6C$e&YbrTct6~8&;5P4=bq=@^W4Mr!*u`v0OADj?>H72 z8}1*03ki?qkT^!@>;39E-PVlh5HSeC*=2vw+ zlp0xNT|F3ew=wYMIoPmb(Ky*v0>;OVC*v;%`j`wZ-X^>IJMav3Khu3;&PrYtHi~

os?$`26el>Y`xz_6E$6w15z$tjc4G|*L{$kGVP zIGE$L`gl-t)tC^4u94MeHRh}F9z2ys=w~>Tsb0lEUXOLr^~GXT&$_c)O;(D}T}01D zQrpAC{O5|`1oKXZQIdh~wMLH;K}J%&+V1gRO#LfrtQkUMwwlhCGgxDzA>YOOqX`n@ ze*06aG1-~C4vS-tn`!&X&(F?o4kc>B1L`v%9%-KM%l3A1kHcDn%+6bT2FLCHRQuK6!@+Ig_9_Tr;$O2>^ZBc2hg|M)%?r_!U7^i}@vS}wD=WNuZ<6?W;!NYA0poXIC z7ctf!qeZ{mSFoQl-ZY(ja9iX`!q&bL9uXu>F2wv zg5tqFx;yT|MvlUxC{97f9Tx412U6duSP1IxEU2`K)nL*!@6L$CjsqLA~{3&~`Qq9`tB-)%6*< zkJ%_OZQ(!3UV}T3tWmJJ57ArZ2*CvdXehZHCai|cW(sUO-|-1R!wwKNp4$jp$(72> zH~>}pc;+Rvp48)7WR-J16Rj&q9RVM5zqC4_8x9zFe-mN;no{QxWYx^|+W(Ai+ZU5J z&`+_f(eIwD}j-&9jb6^X_hIU*+q0oGei%zWNkAng4 zu{8FG%WlZB^ZdMpR?3!A)xa3-<1}z_O;e8Dr3lEJhD}{HjvPp*&Z)DbuT%L=HN28T z`9J%iB=UO9Nd4+D(=3Z}v5U43qqgA;eiE-$x*aOJM_AmaS(m?#*tGlNvee*?vyjlb zvz$8e6>&yt$%|lsT1-yJOz!m0p)~1)nHwOGiYkE%Po5kAqW9Gk-qw5lZH?TQwDggN z001}vNdo_jB!Rpu!5|Ol(d98*ijr0)sui^2I6U=k3{c_Bauhy(-z zZgnEUZu`o>rG1PrWY=k*h;Yerr%>sRGqq4_sMnzx(ec3{?RZ}4ehG7IV@mtQjVyTJ0s{fS zU*(aqNNLWVrD0s+T%w_gD1jqrUqLB~l~rJGBQNDtp!shRITytd5eSfMCl;QSLm2}D z>t7a7x_S+V3JF+lNT=3tvjR~`ukJ=qPl$W!14hl2VksH<<~U6r22A`3?HyN3nN)r& z{W}J>rY6mQZpjU&cGvdK!h}W^i&;3lBJ2|#IrtlBKO3vexEEx%;e^^Y>YKQkF_X1s zgE=v>Ddk!o+38kYE4see`b%A%c3D~d1&Tufse_9 z)wPmC4|dewMTbjnSIxagh{P%O*MAki(u6nrV1?*!n0mvOzEwf!s|+`UU+aW_z*m Date: Wed, 30 Sep 2020 11:42:05 +0300 Subject: [PATCH 06/75] Drop config files --- .gitignore | 4 ++++ OsmAnd/.gitignore | 1 - OsmAndHms.jks | Bin 2243 -> 0 bytes agconnect-services.json | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 OsmAndHms.jks delete mode 100644 agconnect-services.json diff --git a/.gitignore b/.gitignore index 5798e73f27..33e746a3d6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,10 @@ OsmAndCore_*.aar .project out/ +# Huawei +agconnect-services.json +OsmAndHms.jks + # Android Studio /.idea *.iml diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index e1887fffff..e3071e5fbf 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -17,7 +17,6 @@ libs/huawei-*.jar huaweidrmlib/ HwDRM_SDK_* drm_strings.xml -agconnect-services.json # copy_widget_icons.sh res/drawable-large/map_* diff --git a/OsmAndHms.jks b/OsmAndHms.jks deleted file mode 100644 index b1d7e59362b69659660ae0e1bc29bfdca0f751f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2243 zcmchY={Fk)7sj*LnvmF2YF|oi6I+y^YN?2=T1)L)f)cxFETt7KHI!EE65FImETyWn zYS%@Jh*lJ>poUVk7&U6}X6C$e&YbrTct6~8&;5P4=bq=@^W4Mr!*u`v0OADj?>H72 z8}1*03ki?qkT^!@>;39E-PVlh5HSeC*=2vw+ zlp0xNT|F3ew=wYMIoPmb(Ky*v0>;OVC*v;%`j`wZ-X^>IJMav3Khu3;&PrYtHi~

os?$`26el>Y`xz_6E$6w15z$tjc4G|*L{$kGVP zIGE$L`gl-t)tC^4u94MeHRh}F9z2ys=w~>Tsb0lEUXOLr^~GXT&$_c)O;(D}T}01D zQrpAC{O5|`1oKXZQIdh~wMLH;K}J%&+V1gRO#LfrtQkUMwwlhCGgxDzA>YOOqX`n@ ze*06aG1-~C4vS-tn`!&X&(F?o4kc>B1L`v%9%-KM%l3A1kHcDn%+6bT2FLCHRQuK6!@+Ig_9_Tr;$O2>^ZBc2hg|M)%?r_!U7^i}@vS}wD=WNuZ<6?W;!NYA0poXIC z7ctf!qeZ{mSFoQl-ZY(ja9iX`!q&bL9uXu>F2wv zg5tqFx;yT|MvlUxC{97f9Tx412U6duSP1IxEU2`K)nL*!@6L$CjsqLA~{3&~`Qq9`tB-)%6*< zkJ%_OZQ(!3UV}T3tWmJJ57ArZ2*CvdXehZHCai|cW(sUO-|-1R!wwKNp4$jp$(72> zH~>}pc;+Rvp48)7WR-J16Rj&q9RVM5zqC4_8x9zFe-mN;no{QxWYx^|+W(Ai+ZU5J z&`+_f(eIwD}j-&9jb6^X_hIU*+q0oGei%zWNkAng4 zu{8FG%WlZB^ZdMpR?3!A)xa3-<1}z_O;e8Dr3lEJhD}{HjvPp*&Z)DbuT%L=HN28T z`9J%iB=UO9Nd4+D(=3Z}v5U43qqgA;eiE-$x*aOJM_AmaS(m?#*tGlNvee*?vyjlb zvz$8e6>&yt$%|lsT1-yJOz!m0p)~1)nHwOGiYkE%Po5kAqW9Gk-qw5lZH?TQwDggN z001}vNdo_jB!Rpu!5|Ol(d98*ijr0)sui^2I6U=k3{c_Bauhy(-z zZgnEUZu`o>rG1PrWY=k*h;Yerr%>sRGqq4_sMnzx(ec3{?RZ}4ehG7IV@mtQjVyTJ0s{fS zU*(aqNNLWVrD0s+T%w_gD1jqrUqLB~l~rJGBQNDtp!shRITytd5eSfMCl;QSLm2}D z>t7a7x_S+V3JF+lNT=3tvjR~`ukJ=qPl$W!14hl2VksH<<~U6r22A`3?HyN3nN)r& z{W}J>rY6mQZpjU&cGvdK!h}W^i&;3lBJ2|#IrtlBKO3vexEEx%;e^^Y>YKQkF_X1s zgE=v>Ddk!o+38kYE4see`b%A%c3D~d1&Tufse_9 z)wPmC4|dewMTbjnSIxagh{P%O*MAki(u6nrV1?*!n0mvOzEwf!s|+`UU+aW_z*m Date: Wed, 30 Sep 2020 12:56:11 +0300 Subject: [PATCH 07/75] Fixed gradle scripts --- OsmAnd/build.gradle | 1 - build.gradle | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 0aef4aa139..1232b9661f 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -542,6 +542,5 @@ dependencies { } implementation 'com.jaredrummler:colorpicker:1.1.0' - //freehuaweiImplementation 'com.huawei.agconnect:agconnect-core:1.4.1.300' freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' } diff --git a/build.gradle b/build.gradle index 1cd0b221b4..e75ca7d5b8 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,14 @@ buildscript { google() mavenCentral() jcenter() + maven { + url 'https://developer.huawei.com/repo/' + content { + includeGroup 'com.huawei.agconnect' + includeGroup 'com.huawei.hms' + includeGroup 'com.huawei.hmf' + } + } } dependencies { //classpath 'com.android.tools.build:gradle:2.+' @@ -11,6 +19,9 @@ buildscript { classpath 'com.google.gms:google-services:3.0.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + if (gradle.startParameter.taskNames.toString().contains("huawei")) { + classpath 'com.huawei.agconnect:agcp:1.4.1.300' + } } } @@ -32,5 +43,13 @@ allprojects { maven { url "https://jitpack.io" } + maven { + url 'https://developer.huawei.com/repo/' + content { + includeGroup 'com.huawei.agconnect' + includeGroup 'com.huawei.hms' + includeGroup 'com.huawei.hmf' + } + } } } From b01e72b00d6c3190c3cb273007be86a62192a81e Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 1 Oct 2020 11:06:52 +0300 Subject: [PATCH 08/75] Add the ability to update ContextMenuItem itself --- .../net/osmand/plus/ContextMenuAdapter.java | 10 +------ .../src/net/osmand/plus/ContextMenuItem.java | 27 +++++++++++++++-- .../osmand/plus/dashboard/DashboardOnMap.java | 28 +++++++++--------- .../osmand/plus/dialogs/ConfigureMapMenu.java | 29 +++++++------------ 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java index 795bc7224c..0842a1923e 100644 --- a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java +++ b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java @@ -65,7 +65,6 @@ public class ContextMenuAdapter { @LayoutRes private int DEFAULT_LAYOUT_ID = R.layout.list_menu_item_native; List items = new ArrayList<>(); - private ArrayAdapter arrayAdapter; private boolean profileDependent = false; private boolean nightMode; private ConfigureMapMenu.OnClickListener changeAppModeListener = null; @@ -202,9 +201,8 @@ public class ContextMenuAdapter { } } items.removeAll(itemsToRemove); - arrayAdapter = new ContextMenuArrayAdapter(activity, layoutId, R.id.title, + return new ContextMenuArrayAdapter(activity, layoutId, R.id.title, items.toArray(new ContextMenuItem[items.size()]), app, lightTheme, changeAppModeListener); - return arrayAdapter; } public class ContextMenuArrayAdapter extends ArrayAdapter { @@ -627,12 +625,6 @@ public class ContextMenuAdapter { return visible; } - public void notifyDataSetChanged() { - if (arrayAdapter != null) { - arrayAdapter.notifyDataSetChanged(); - } - } - public static OnItemDeleteAction makeDeleteAction(final OsmandPreference... prefs) { return new OnItemDeleteAction() { @Override diff --git a/OsmAnd/src/net/osmand/plus/ContextMenuItem.java b/OsmAnd/src/net/osmand/plus/ContextMenuItem.java index 3a5b391a67..3718169532 100644 --- a/OsmAnd/src/net/osmand/plus/ContextMenuItem.java +++ b/OsmAnd/src/net/osmand/plus/ContextMenuItem.java @@ -34,6 +34,7 @@ public class ContextMenuItem { private boolean hidden; private int order; private String description; + private final OnUpdateCallback onUpdateCallback; private final ContextMenuAdapter.ItemClickListener itemClickListener; private final ContextMenuAdapter.OnIntegerValueChangedListener integerListener; private final ContextMenuAdapter.ProgressListener progressListener; @@ -58,6 +59,7 @@ public class ContextMenuItem { boolean skipPaintingWithoutColor, int order, String description, + OnUpdateCallback onUpdateCallback, ContextMenuAdapter.ItemClickListener itemClickListener, ContextMenuAdapter.OnIntegerValueChangedListener integerListener, ContextMenuAdapter.ProgressListener progressListener, @@ -81,6 +83,7 @@ public class ContextMenuItem { this.skipPaintingWithoutColor = skipPaintingWithoutColor; this.order = order; this.description = description; + this.onUpdateCallback = onUpdateCallback; this.itemClickListener = itemClickListener; this.integerListener = integerListener; this.progressListener = progressListener; @@ -245,6 +248,16 @@ public class ContextMenuItem { return id; } + public void update() { + if (onUpdateCallback != null) { + onUpdateCallback.onUpdateMenuItem(this); + } + } + + public interface OnUpdateCallback { + void onUpdateMenuItem(ContextMenuItem item); + } + public static ItemBuilder createBuilder(String title) { return new ItemBuilder().setTitle(title); } @@ -268,6 +281,7 @@ public class ContextMenuItem { private boolean mIsClickable = true; private int mOrder = 0; private String mDescription = null; + private OnUpdateCallback mOnUpdateCallback = null; private ContextMenuAdapter.ItemClickListener mItemClickListener = null; private ContextMenuAdapter.OnIntegerValueChangedListener mIntegerListener = null; private ContextMenuAdapter.ProgressListener mProgressListener = null; @@ -348,6 +362,11 @@ public class ContextMenuItem { return this; } + public ItemBuilder setOnUpdateCallback(OnUpdateCallback onUpdateCallback) { + mOnUpdateCallback = onUpdateCallback; + return this; + } + public ItemBuilder setListener(ContextMenuAdapter.ItemClickListener checkBoxListener) { mItemClickListener = checkBoxListener; return this; @@ -403,10 +422,12 @@ public class ContextMenuItem { } public ContextMenuItem createItem() { - return new ContextMenuItem(mTitleId, mTitle, mIcon, mColorRes, mSecondaryIcon, + ContextMenuItem item = new ContextMenuItem(mTitleId, mTitle, mIcon, mColorRes, mSecondaryIcon, mSelected, mProgress, mLayout, mLoading, mIsCategory, mIsClickable, mSkipPaintingWithoutColor, - mOrder, mDescription, mItemClickListener, mIntegerListener, mProgressListener, mItemDeleteAction, - mHideDivider, mHideCompoundButton, mMinHeight, mTag, mId); + mOrder, mDescription, mOnUpdateCallback, mItemClickListener, mIntegerListener, mProgressListener, + mItemDeleteAction, mHideDivider, mHideCompoundButton, mMinHeight, mTag, mId); + item.update(); + return item; } } } diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index 4c2c4a6a8d..a7bb87db2a 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -96,8 +96,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_STYLE_ID; - public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInformationListener { private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(DashboardOnMap.class); @@ -132,7 +130,6 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo private ArrayAdapter listAdapter; private OnItemClickListener listAdapterOnClickListener; - private ConfigureMapMenu configureMapMenu; private boolean visible = false; private DashboardType visibleType; @@ -659,16 +656,10 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo fragment.show(mapActivity.getSupportFragmentManager(), MapillaryFirstDialogFragment.TAG); settings.MAPILLARY_FIRST_DIALOG_SHOWN.set(true); } - - deleteTmpReferences(); } mapActivity.updateStatusBarColor(); } - private void deleteTmpReferences() { - configureMapMenu = null; - } - public void updateDashboard() { if (visibleType == DashboardType.ROUTE_PREFERENCES) { refreshContent(false); @@ -714,8 +705,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo if (visibleType == DashboardType.CONFIGURE_SCREEN) { cm = mapActivity.getMapLayers().getMapWidgetRegistry().getViewConfigureMenuAdapter(mapActivity); } else if (visibleType == DashboardType.CONFIGURE_MAP) { - configureMapMenu = new ConfigureMapMenu(mapActivity); - cm = configureMapMenu.createListAdapter(); + cm = new ConfigureMapMenu(mapActivity).createListAdapter(); } else if (visibleType == DashboardType.LIST_MENU) { cm = mapActivity.getMapActions().createMainOptionsMenu(); } else if (visibleType == DashboardType.ROUTE_PREFERENCES) { @@ -1043,8 +1033,20 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo } public void onMapSettingsUpdated() { - if (configureMapMenu != null) { - configureMapMenu.updateMenuItem(MAP_STYLE_ID); + if (DashboardType.CONFIGURE_MAP.equals(visibleType)) { + updateMenuItems(); + } + } + + public void updateMenuItems() { + if (listAdapter != null) { + for (int i = 0; i < listAdapter.getCount(); i++) { + Object o = listAdapter.getItem(i); + if (o instanceof ContextMenuItem) { + ((ContextMenuItem) o).update(); + } + } + listAdapter.notifyDataSetChanged(); } } diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index d9a60adff3..ea1934c5e6 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -111,7 +111,6 @@ public class ConfigureMapMenu { private boolean transliterateNames; private MapActivity ma; - private ContextMenuAdapter contextMenuAdapter; public interface OnClickListener { void onClick(); @@ -143,7 +142,6 @@ public class ConfigureMapMenu { adapter.setNightMode(nightMode); createLayersItems(customRules, adapter, ma, themeRes, nightMode); createRenderingAttributeItems(customRules, adapter, ma, themeRes, nightMode); - this.contextMenuAdapter = adapter; return adapter; } @@ -281,16 +279,16 @@ public class ConfigureMapMenu { final OsmandSettings settings = app.getSettings(); final int selectedProfileColorRes = settings.APPLICATION_MODE.get().getIconColorInfo().getColor(nightMode); final int selectedProfileColor = ContextCompat.getColor(app, selectedProfileColorRes); - String renderDescr = getRenderDescr(app); adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_map_rendering, activity) .setId(MAP_RENDERING_CATEGORY_ID) .setCategory(true).setLayout(R.layout.list_group_title_with_switch).createItem()); - adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.map_widget_renderer, activity) + adapter.addItem(new ContextMenuItem.ItemBuilder() .setId(MAP_STYLE_ID) - .setDescription(renderDescr) + .setTitleId(R.string.map_widget_renderer, activity) .setLayout(R.layout.list_item_single_line_descrition_narrow) - .setIcon(R.drawable.ic_map).setListener(new ContextMenuAdapter.ItemClickListener() { + .setIcon(R.drawable.ic_map) + .setListener(new ContextMenuAdapter.ItemClickListener() { @Override public boolean onContextMenuClick(final ArrayAdapter ad, int itemId, final int pos, boolean isChecked, int[] viewCoordinates) { @@ -300,6 +298,13 @@ public class ConfigureMapMenu { } }) .setItemDeleteAction(makeDeleteAction(settings.RENDERER)) + .setOnUpdateCallback(new ContextMenuItem.OnUpdateCallback() { + @Override + public void onUpdateMenuItem(ContextMenuItem item) { + String renderDescr = getRenderDescr(app); + item.setDescription(renderDescr); + } + }) .createItem()); String description = ""; @@ -1119,18 +1124,6 @@ public class ConfigureMapMenu { } } - public void updateMenuItem(String itemId) { - OsmandApplication app = getMyApplication(); - if (app == null) return; - - if (MAP_STYLE_ID.equals(itemId) && contextMenuAdapter != null) { - ContextMenuItem item = contextMenuAdapter.getItemById(MAP_STYLE_ID); - String renderDescr = getRenderDescr(app); - item.setDescription(renderDescr); - contextMenuAdapter.notifyDataSetChanged(); - } - } - private OsmandApplication getMyApplication() { return ma.getMyApplication(); } From f24e491ebb252d7bf653386cac0167bb1f9b3027 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 1 Oct 2020 11:13:02 +0300 Subject: [PATCH 09/75] delete unnecessary code --- OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java | 9 --------- OsmAnd/src/net/osmand/plus/render/RendererRegistry.java | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java index 0842a1923e..7263f119bd 100644 --- a/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java +++ b/OsmAnd/src/net/osmand/plus/ContextMenuAdapter.java @@ -100,15 +100,6 @@ public class ContextMenuAdapter { return items.get(position); } - public ContextMenuItem getItemById(@NonNull String id) { - for (ContextMenuItem item : items) { - if (id.equals(item.getId())) { - return item; - } - } - return null; - } - public List getItems() { return items; } diff --git a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java index d382fa9882..3f8ed81ba7 100644 --- a/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java +++ b/OsmAnd/src/net/osmand/plus/render/RendererRegistry.java @@ -334,7 +334,7 @@ public class RendererRegistry { return currentSelectedRender; } - public void setCurrentSelectedRender(final RenderingRulesStorage currentSelectedRender) { + public void setCurrentSelectedRender(RenderingRulesStorage currentSelectedRender) { this.currentSelectedRender = currentSelectedRender; } From b594076f142978438b297d18b8cffbcc8c43c3cd Mon Sep 17 00:00:00 2001 From: MadWasp79 Date: Thu, 1 Oct 2020 14:03:06 +0300 Subject: [PATCH 10/75] #9853 fix calc time in android --- .../src/main/java/net/osmand/router/RoutePlannerFrontEnd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java index 54464afc32..bff7db3a6d 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java @@ -739,7 +739,7 @@ public class RoutePlannerFrontEnd { res = searchRouteImpl(ctx, points, routeDirection); } if (ctx.calculationProgress != null) { - ctx.calculationProgress.timeToCalculate += (System.nanoTime() - timeToCalculate); + ctx.calculationProgress.timeToCalculate = (System.nanoTime() - timeToCalculate); } BinaryRoutePlanner.printDebugMemoryInformation(ctx); if (res != null) { From 8b0285031a6e06b89e3617e3e89042201819c568 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Thu, 1 Oct 2020 14:51:43 +0300 Subject: [PATCH 11/75] Fix track arrows --- .../views/layers/geometry/GeometryWayContext.java | 15 ++++++++++++++- .../views/layers/geometry/GeometryWayDrawer.java | 14 ++++++-------- .../views/layers/geometry/GpxGeometryWay.java | 1 + .../layers/geometry/GpxGeometryWayContext.java | 13 ++++++++++++- .../PublicTransportGeometryWayContext.java | 5 +++++ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java index d53160bd04..16efb3a69b 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java @@ -121,4 +121,17 @@ public abstract class GeometryWayContext { return arrowBitmap; } -} + public double getPxStep(double zoomCoef) { + return getDefaultPxStep(zoomCoef); + } + + public double getPxStepRegular(double zoomCoef) { + return getDefaultPxStep(zoomCoef); + } + + public double getDefaultPxStep(double zoomCoef) { + Bitmap arrow = getArrowBitmap(); + int arrowHeight = arrow.getHeight(); + return arrowHeight * 4f * zoomCoef; + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java index db45a9b55c..18039c35d6 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java @@ -31,22 +31,20 @@ public class GeometryWayDrawer { int h = tb.getPixHeight(); int w = tb.getPixWidth(); - int left = -w / 4; + int left = -w / 4; int right = w + w / 4; - int top = - h/4; - int bottom = h + h/4; + int top = -h / 4; + int bottom = h + h / 4; boolean hasStyles = styles != null && styles.size() == tx.size(); double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f; - Bitmap arrow = context.getArrowBitmap(); - int arrowHeight = arrow.getHeight(); - double pxStep = arrowHeight * 4f * zoomCoef; - double pxStepRegular = arrowHeight * 4f * zoomCoef; + double pxStep = context.getPxStep(zoomCoef); + double pxStepRegular = context.getPxStepRegular(zoomCoef); double dist = 0; if (distPixToFinish != 0) { dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1 } - for (int i = tx.size() - 2; i >= 0; i --) { + for (int i = tx.size() - 2; i >= 0; i--) { GeometryWayStyle style = hasStyles ? styles.get(i) : null; float px = tx.get(i); float py = ty.get(i); diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java index 24d49df0b1..d51d9e0231 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java @@ -48,6 +48,7 @@ public class GpxGeometryWay extends GeometryWay Date: Thu, 1 Oct 2020 15:33:22 +0300 Subject: [PATCH 12/75] Add phrase --- OsmAnd/res/values/phrases.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 3f4394578a..b58d25a781 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4257,5 +4257,7 @@ Nut store + LNG + From e0d266ff0d2959923cd87c3689ae3f8cc3b64ef2 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Thu, 1 Oct 2020 15:38:36 +0300 Subject: [PATCH 13/75] remove unnecessary changes --- .../views/layers/geometry/GeometryWayContext.java | 15 +-------------- .../views/layers/geometry/GeometryWayDrawer.java | 13 ++++++++++--- .../views/layers/geometry/GpxGeometryWay.java | 1 - .../layers/geometry/GpxGeometryWayContext.java | 13 +------------ .../PublicTransportGeometryWayContext.java | 5 ----- 5 files changed, 12 insertions(+), 35 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java index 16efb3a69b..d53160bd04 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayContext.java @@ -121,17 +121,4 @@ public abstract class GeometryWayContext { return arrowBitmap; } - public double getPxStep(double zoomCoef) { - return getDefaultPxStep(zoomCoef); - } - - public double getPxStepRegular(double zoomCoef) { - return getDefaultPxStep(zoomCoef); - } - - public double getDefaultPxStep(double zoomCoef) { - Bitmap arrow = getArrowBitmap(); - int arrowHeight = arrow.getHeight(); - return arrowHeight * 4f * zoomCoef; - } -} \ No newline at end of file +} diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java index 18039c35d6..ac6f514ed1 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java @@ -38,8 +38,15 @@ public class GeometryWayDrawer { boolean hasStyles = styles != null && styles.size() == tx.size(); double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f; - double pxStep = context.getPxStep(zoomCoef); - double pxStepRegular = context.getPxStepRegular(zoomCoef); + Bitmap arrow = context.getArrowBitmap(); + int arrowHeight = arrow.getHeight(); + double defaultPxStep; + if (hasStyles && styles.get(0) != null) { + defaultPxStep = styles.get(0).getPointStepPx(zoomCoef); + } else { + defaultPxStep = arrowHeight * 4f * zoomCoef; + } + double pxStep = defaultPxStep; double dist = 0; if (distPixToFinish != 0) { dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1 @@ -55,7 +62,7 @@ public class GeometryWayDrawer { if (distSegment == 0) { continue; } - pxStep = style != null ? style.getPointStepPx(zoomCoef) : pxStepRegular; + pxStep = style != null ? style.getPointStepPx(zoomCoef) : defaultPxStep; if (dist >= pxStep) { dist = 0; } diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java index d51d9e0231..24d49df0b1 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GpxGeometryWay.java @@ -48,7 +48,6 @@ public class GpxGeometryWay extends GeometryWay Date: Thu, 1 Oct 2020 15:51:08 +0300 Subject: [PATCH 14/75] Fix start style index --- .../views/layers/geometry/GeometryWayDrawer.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java index ac6f514ed1..0bfa86f43b 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWayDrawer.java @@ -38,20 +38,21 @@ public class GeometryWayDrawer { boolean hasStyles = styles != null && styles.size() == tx.size(); double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f; - Bitmap arrow = context.getArrowBitmap(); - int arrowHeight = arrow.getHeight(); + + int startIndex = tx.size() - 2; double defaultPxStep; - if (hasStyles && styles.get(0) != null) { - defaultPxStep = styles.get(0).getPointStepPx(zoomCoef); + if (hasStyles && styles.get(startIndex) != null) { + defaultPxStep = styles.get(startIndex).getPointStepPx(zoomCoef); } else { - defaultPxStep = arrowHeight * 4f * zoomCoef; + Bitmap arrow = context.getArrowBitmap(); + defaultPxStep = arrow.getHeight() * 4f * zoomCoef; } double pxStep = defaultPxStep; double dist = 0; if (distPixToFinish != 0) { dist = distPixToFinish - pxStep * ((int) (distPixToFinish / pxStep)); // dist < 1 } - for (int i = tx.size() - 2; i >= 0; i--) { + for (int i = startIndex; i >= 0; i--) { GeometryWayStyle style = hasStyles ? styles.get(i) : null; float px = tx.get(i); float py = ty.get(i); From ac78952962b33c274445007c28198c9c56392ac7 Mon Sep 17 00:00:00 2001 From: Nazar-Kutz Date: Thu, 1 Oct 2020 17:51:03 +0300 Subject: [PATCH 15/75] delete unnecessary code --- .../net/osmand/plus/dashboard/DashboardOnMap.java | 6 +++--- .../net/osmand/plus/dialogs/ConfigureMapMenu.java | 14 ++------------ .../fragments/ConfigureMenuItemsFragment.java | 4 ++-- .../fragments/ConfigureMenuRootFragment.java | 4 ++-- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java index a7bb87db2a..bb2143c7eb 100644 --- a/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java +++ b/OsmAnd/src/net/osmand/plus/dashboard/DashboardOnMap.java @@ -567,8 +567,8 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo boolean appModeChanged = currentAppMode != previousAppMode; boolean refresh = this.visibleType == type && !appModeChanged; - this.previousAppMode = currentAppMode; - this.visibleType = type; + previousAppMode = currentAppMode; + visibleType = type; DashboardOnMap.staticVisible = visible; DashboardOnMap.staticVisibleType = type; mapActivity.enableDrawer(); @@ -705,7 +705,7 @@ public class DashboardOnMap implements ObservableScrollViewCallbacks, IRouteInfo if (visibleType == DashboardType.CONFIGURE_SCREEN) { cm = mapActivity.getMapLayers().getMapWidgetRegistry().getViewConfigureMenuAdapter(mapActivity); } else if (visibleType == DashboardType.CONFIGURE_MAP) { - cm = new ConfigureMapMenu(mapActivity).createListAdapter(); + cm = new ConfigureMapMenu().createListAdapter(mapActivity); } else if (visibleType == DashboardType.LIST_MENU) { cm = mapActivity.getMapActions().createMainOptionsMenu(); } else if (visibleType == DashboardType.ROUTE_PREFERENCES) { diff --git a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java index ea1934c5e6..83c4e1942d 100644 --- a/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java +++ b/OsmAnd/src/net/osmand/plus/dialogs/ConfigureMapMenu.java @@ -110,17 +110,11 @@ public class ConfigureMapMenu { private int selectedLanguageIndex; private boolean transliterateNames; - private MapActivity ma; - public interface OnClickListener { void onClick(); } - public ConfigureMapMenu(MapActivity mapActivity) { - this.ma = mapActivity; - } - - public ContextMenuAdapter createListAdapter() { + public ContextMenuAdapter createListAdapter(final MapActivity ma) { OsmandApplication app = ma.getMyApplication(); boolean nightMode = app.getDaynightHelper().isNightModeForMapControls(); int themeRes = nightMode ? R.style.OsmandDarkTheme : R.style.OsmandLightTheme; @@ -133,7 +127,7 @@ public class ConfigureMapMenu { adapter.setChangeAppModeListener(new OnClickListener() { @Override public void onClick() { - ma.getDashboard().updateListAdapter(createListAdapter()); + ma.getDashboard().updateListAdapter(createListAdapter(ma)); } }); List customRules = getCustomRules(app, @@ -1124,10 +1118,6 @@ public class ConfigureMapMenu { } } - private OsmandApplication getMyApplication() { - return ma.getMyApplication(); - } - private static class StringSpinnerArrayAdapter extends ArrayAdapter { private boolean nightMode; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java index 926264aceb..44fb4bf7f6 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuItemsFragment.java @@ -197,8 +197,8 @@ public class ConfigureMenuItemsFragment extends BaseOsmAndFragment contextMenuAdapter = ((MapActivity) activity).getMapActions().createMainOptionsMenu(); break; case CONFIGURE_MAP: - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); - contextMenuAdapter = configureMapMenu.createListAdapter(); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(); + contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); break; case CONTEXT_MENU_ACTIONS: MapContextMenu menu = ((MapActivity) activity).getContextMenu(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java index f7b2dda412..330f1c0a18 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ConfigureMenuRootFragment.java @@ -285,8 +285,8 @@ public class ConfigureMenuRootFragment extends BaseOsmAndFragment { contextMenuAdapter = mapActivityActions.createMainOptionsMenu(); break; case CONFIGURE_MAP: - ConfigureMapMenu configureMapMenu = new ConfigureMapMenu((MapActivity) activity); - contextMenuAdapter = configureMapMenu.createListAdapter(); + ConfigureMapMenu configureMapMenu = new ConfigureMapMenu(); + contextMenuAdapter = configureMapMenu.createListAdapter((MapActivity) activity); break; case CONTEXT_MENU_ACTIONS: MapContextMenu menu = ((MapActivity) activity).getContextMenu(); From b7bbe55dad6678ed93f38fcf7b533038fc0e699a Mon Sep 17 00:00:00 2001 From: max-klaus Date: Thu, 1 Oct 2020 19:14:46 +0300 Subject: [PATCH 16/75] Huawei inapps/subs introduced --- .../plus/inapp/InAppPurchaseHelperImpl.java | 62 +- .../osmand/plus/inapp/InAppPurchasesImpl.java | 323 +++++++ .../net/osmand/plus/inapp/CipherUtil.java | 96 ++ .../net/osmand/plus/inapp/Constants.java | 33 + .../osmand/plus/inapp/ExceptionHandle.java | 103 +++ .../net/osmand/plus/inapp/IapApiCallback.java | 37 + .../osmand/plus/inapp/IapRequestHelper.java | 351 +++++++ .../plus/inapp/InAppPurchaseHelperImpl.java | 861 +++++++++++------- .../osmand/plus/inapp/InAppPurchasesImpl.java | 184 ++++ .../net/osmand/plus/inapp/InAppUtils.java | 49 + .../osmand/plus/inapp/SubscriptionUtils.java | 139 +++ .../OsmandInAppPurchaseActivity.java | 70 +- .../osmand/plus/helpers/DiscountHelper.java | 10 +- .../plus/inapp/InAppPurchaseHelper.java | 58 +- .../net/osmand/plus/inapp/InAppPurchases.java | 407 +-------- 15 files changed, 1987 insertions(+), 796 deletions(-) create mode 100644 OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchasesImpl.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/Constants.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/ExceptionHandle.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/IapApiCallback.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/IapRequestHelper.java create mode 100644 OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java create mode 100644 OsmAnd/src-huawei/net/osmand/plus/inapp/InAppUtils.java create mode 100755 OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index c48294aeb2..fc9b1056f6 100644 --- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -13,6 +13,8 @@ import com.android.billingclient.api.SkuDetailsResponseListener; import net.osmand.AndroidUtils; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; +import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.util.BillingManager; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.Algorithms; @@ -49,13 +51,21 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { public InAppPurchaseHelperImpl(OsmandApplication ctx) { super(ctx); + purchases = new InAppPurchasesImpl(ctx); + } + + @Override + public void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback) { + if (callback != null) { + callback.onSuccess(); + } } private BillingManager getBillingManager() { return billingManager; } - protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand runnable) { billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { @Override @@ -77,7 +87,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { return; } - processingTask = !runnable.run(InAppPurchaseHelperImpl.this); + runnable.run(InAppPurchaseHelperImpl.this); } @Override @@ -114,7 +124,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } List skuSubscriptions = new ArrayList<>(); - for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { + for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { skuSubscriptions.add(subscription.getSku()); } for (Purchase p : purchases) { @@ -165,9 +175,9 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { public void purchaseFullVersion(final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); - exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() { + exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppCommand() { @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { try { SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku()); if (skuDetails == null) { @@ -179,22 +189,21 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } else { throw new IllegalStateException("BillingManager disposed"); } - return false; + commandDone(); } catch (Exception e) { complain("Cannot launch full version purchase!"); logError("purchaseFullVersion Error", e); stop(true); } - return true; } }); } public void purchaseDepthContours(final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); - exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() { + exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppCommand() { @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { try { SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku()); if (skuDetails == null) { @@ -206,13 +215,12 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } else { throw new IllegalStateException("BillingManager disposed"); } - return false; + commandDone(); } catch (Exception e) { complain("Cannot launch depth contours purchase!"); logError("purchaseDepthContours Error", e); stop(true); } - return true; } }); } @@ -295,7 +303,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { */ List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); - for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { if (hasDetails(s.getSku())) { Purchase purchase = getPurchase(s.getSku()); SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); @@ -309,9 +317,9 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { Purchase purchase = getPurchase(sku); SkuDetails liveUpdatesDetails = getSkuDetails(sku); if (liveUpdatesDetails != null) { - InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); if (s == null) { - s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); + s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); } fetchInAppPurchase(s, liveUpdatesDetails, purchase); } @@ -441,21 +449,21 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); if (!Algorithms.isEmpty(subscriptionPeriod)) { - if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + if (inAppPurchase instanceof InAppSubscription) { try { - ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + ((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); } catch (ParseException e) { LOG.error(e); } } } - if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + if (inAppPurchase instanceof InAppSubscription) { String introductoryPrice = skuDetails.getIntroductoryPrice(); String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); if (!Algorithms.isEmpty(introductoryPrice)) { - InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + InAppSubscription s = (InAppSubscription) inAppPurchase; try { s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); @@ -466,10 +474,10 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { - return new InAppRunnable() { + protected InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppCommand() { @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { try { Activity a = activity.get(); SkuDetails skuDetails = getSkuDetails(sku); @@ -481,7 +489,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } else { throw new IllegalStateException("BillingManager disposed"); } - return false; + commandDone(); } else { stop(true); } @@ -489,15 +497,14 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { logError("launchPurchaseFlow Error", e); stop(true); } - return true; } }; } - protected InAppRunnable getRequestInventoryCommand() { - return new InAppRunnable() { + protected InAppCommand getRequestInventoryCommand() { + return new InAppCommand() { @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { logDebug("Setup successful. Querying inventory."); try { BillingManager billingManager = getBillingManager(); @@ -506,13 +513,12 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } else { throw new IllegalStateException("BillingManager disposed"); } - return false; + commandDone(); } catch (Exception e) { logError("queryInventoryAsync Error", e); notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); stop(true); } - return true; } }; } diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchasesImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchasesImpl.java new file mode 100644 index 0000000000..a2a0f8d680 --- /dev/null +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchasesImpl.java @@ -0,0 +1,323 @@ +package net.osmand.plus.inapp; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.SkuDetails; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.Version; + +public class InAppPurchasesImpl extends InAppPurchases { + + private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion(); + private static final InAppPurchaseDepthContoursFull DEPTH_CONTOURS_FULL = new InAppPurchaseDepthContoursFull(); + private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree(); + private static final InAppPurchaseContourLinesFull CONTOUR_LINES_FULL = new InAppPurchaseContourLinesFull(); + private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree(); + + private static final InAppSubscription[] LIVE_UPDATES_FULL = new InAppSubscription[]{ + new InAppPurchaseLiveUpdatesOldMonthlyFull(), + new InAppPurchaseLiveUpdatesMonthlyFull(), + new InAppPurchaseLiveUpdates3MonthsFull(), + new InAppPurchaseLiveUpdatesAnnualFull() + }; + + private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{ + new InAppPurchaseLiveUpdatesOldMonthlyFree(), + new InAppPurchaseLiveUpdatesMonthlyFree(), + new InAppPurchaseLiveUpdates3MonthsFree(), + new InAppPurchaseLiveUpdatesAnnualFree() + }; + + public InAppPurchasesImpl(OsmandApplication ctx) { + super(ctx); + fullVersion = FULL_VERSION; + if (Version.isFreeVersion(ctx)) { + liveUpdates = new LiveUpdatesInAppPurchasesFree(); + } else { + liveUpdates = new LiveUpdatesInAppPurchasesFull(); + } + for (InAppSubscription s : liveUpdates.getAllSubscriptions()) { + if (s instanceof InAppPurchaseLiveUpdatesMonthly) { + if (s.isDiscounted()) { + discountedMonthlyLiveUpdates = s; + } else { + monthlyLiveUpdates = s; + } + } + } + if (Version.isFreeVersion(ctx)) { + depthContours = DEPTH_CONTOURS_FREE; + } else { + depthContours = DEPTH_CONTOURS_FULL; + } + if (Version.isFreeVersion(ctx)) { + contourLines = CONTOUR_LINES_FREE; + } else { + contourLines = CONTOUR_LINES_FULL; + } + + inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines }; + } + + @Override + public boolean isFullVersion(String sku) { + return FULL_VERSION.getSku().equals(sku); + } + + @Override + public boolean isDepthContours(String sku) { + return DEPTH_CONTOURS_FULL.getSku().equals(sku) || DEPTH_CONTOURS_FREE.getSku().equals(sku); + } + + @Override + public boolean isContourLines(String sku) { + return CONTOUR_LINES_FULL.getSku().equals(sku) || CONTOUR_LINES_FREE.getSku().equals(sku); + } + + @Override + public boolean isLiveUpdates(String sku) { + for (InAppPurchase p : LIVE_UPDATES_FULL) { + if (p.getSku().equals(sku)) { + return true; + } + } + for (InAppPurchase p : LIVE_UPDATES_FREE) { + if (p.getSku().equals(sku)) { + return true; + } + } + return false; + } + + private static class InAppPurchaseFullVersion extends InAppPurchase { + + private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; + + InAppPurchaseFullVersion() { + super(SKU_FULL_VERSION_PRICE); + } + + @Override + public String getDefaultPrice(Context ctx) { + return ctx.getString(R.string.full_version_price); + } + } + + private static class InAppPurchaseDepthContoursFull extends InAppPurchaseDepthContours { + + private static final String SKU_DEPTH_CONTOURS_FULL = "net.osmand.seadepth_plus"; + + InAppPurchaseDepthContoursFull() { + super(SKU_DEPTH_CONTOURS_FULL); + } + } + + private static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours { + + private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth"; + + InAppPurchaseDepthContoursFree() { + super(SKU_DEPTH_CONTOURS_FREE); + } + } + + private static class InAppPurchaseContourLinesFull extends InAppPurchaseContourLines { + + private static final String SKU_CONTOUR_LINES_FULL = "net.osmand.contourlines_plus"; + + InAppPurchaseContourLinesFull() { + super(SKU_CONTOUR_LINES_FULL); + } + } + + private static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines { + + private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines"; + + InAppPurchaseContourLinesFree() { + super(SKU_CONTOUR_LINES_FREE); + } + } + + private static class InAppPurchaseLiveUpdatesMonthlyFull extends InAppPurchaseLiveUpdatesMonthly { + + private static final String SKU_LIVE_UPDATES_MONTHLY_FULL = "osm_live_subscription_monthly_full"; + + InAppPurchaseLiveUpdatesMonthlyFull() { + super(SKU_LIVE_UPDATES_MONTHLY_FULL, 1); + } + + private InAppPurchaseLiveUpdatesMonthlyFull(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFull(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly { + + private static final String SKU_LIVE_UPDATES_MONTHLY_FREE = "osm_live_subscription_monthly_free"; + + InAppPurchaseLiveUpdatesMonthlyFree() { + super(SKU_LIVE_UPDATES_MONTHLY_FREE, 1); + } + + private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdates3MonthsFull extends InAppPurchaseLiveUpdates3Months { + + private static final String SKU_LIVE_UPDATES_3_MONTHS_FULL = "osm_live_subscription_3_months_full"; + + InAppPurchaseLiveUpdates3MonthsFull() { + super(SKU_LIVE_UPDATES_3_MONTHS_FULL, 1); + } + + private InAppPurchaseLiveUpdates3MonthsFull(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFull(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months { + + private static final String SKU_LIVE_UPDATES_3_MONTHS_FREE = "osm_live_subscription_3_months_free"; + + InAppPurchaseLiveUpdates3MonthsFree() { + super(SKU_LIVE_UPDATES_3_MONTHS_FREE, 1); + } + + private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdatesAnnualFull extends InAppPurchaseLiveUpdatesAnnual { + + private static final String SKU_LIVE_UPDATES_ANNUAL_FULL = "osm_live_subscription_annual_full"; + + InAppPurchaseLiveUpdatesAnnualFull() { + super(SKU_LIVE_UPDATES_ANNUAL_FULL, 1); + } + + private InAppPurchaseLiveUpdatesAnnualFull(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFull(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual { + + private static final String SKU_LIVE_UPDATES_ANNUAL_FREE = "osm_live_subscription_annual_free"; + + InAppPurchaseLiveUpdatesAnnualFree() { + super(SKU_LIVE_UPDATES_ANNUAL_FREE, 1); + } + + private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdatesOldMonthlyFull extends InAppPurchaseLiveUpdatesOldMonthly { + + private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FULL = "osm_live_subscription_2"; + + InAppPurchaseLiveUpdatesOldMonthlyFull() { + super(SKU_LIVE_UPDATES_OLD_MONTHLY_FULL); + } + } + + private static class InAppPurchaseLiveUpdatesOldMonthlyFree extends InAppPurchaseLiveUpdatesOldMonthly { + + private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FREE = "osm_free_live_subscription_2"; + + InAppPurchaseLiveUpdatesOldMonthlyFree() { + super(SKU_LIVE_UPDATES_OLD_MONTHLY_FREE); + } + } + + public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription { + + private SkuDetails details; + + InAppPurchaseLiveUpdatesOldSubscription(@NonNull SkuDetails details) { + super(details.getSku(), true); + this.details = details; + } + + @Override + public String getDefaultPrice(Context ctx) { + return ""; + } + + @Override + public CharSequence getTitle(Context ctx) { + return details.getTitle(); + } + + @Override + public CharSequence getDescription(@NonNull Context ctx) { + return details.getDescription(); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return null; + } + } + + private static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList { + + public LiveUpdatesInAppPurchasesFree() { + super(LIVE_UPDATES_FREE); + } + } + + private static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList { + + public LiveUpdatesInAppPurchasesFull() { + super(LIVE_UPDATES_FULL); + } + } +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java new file mode 100755 index 0000000000..2bfef4b76b --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java @@ -0,0 +1,96 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * Signature related tools. + * + * @since 2019/12/9 + */ +public class CipherUtil { + private static final String TAG = "CipherUtil"; + private static final String SIGN_ALGORITHMS = "SHA256WithRSA"; + private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAooen3X9jSWarxugznzzMSvp4zir1Pg6uPOm7fqlLOL0Ix52e5FpeotMx871pQ9hrCkiyFg2e6UxD8IXXjvK6QJQbjNJ2jIfKkCusm90yloSEfvyLeiq5y7zg4+DoPglHi8RxZ9y308YIqnRDoslfGm5DnWa8RKUvFRVRiu1p3FN4SYIa/FWLtS5yygemtqMJi8I14V7xqQ5wExCGeSA6j1/AAWXEwZncJwKn0BTXQSvwVBPBRM5ksgt4q+Sc484ZIbntATyxsUipnEBFxq1OXn5Zw5/vVxUC8RSyDMQ/kC2RaEcFtA1tlIIjIdurbpNg3tyViPfQUQndvOs4nDrFzwIDAQAB"; + + /** + * the method to check the signature for the data returned from the interface + * @param content Unsigned data + * @param sign the signature for content + * @param publicKey the public of the application + * @return boolean + */ + public static boolean doCheck(String content, String sign, String publicKey) { + if (TextUtils.isEmpty(publicKey)) { + Log.e(TAG, "publicKey is null"); + return false; + } + + if (TextUtils.isEmpty(content) || TextUtils.isEmpty(sign)) { + Log.e(TAG, "data is error"); + return false; + } + + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] encodedKey = Base64.decode(publicKey, Base64.DEFAULT); + PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); + + java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS); + + signature.initVerify(pubKey); + signature.update(content.getBytes("utf-8")); + + boolean bverify = signature.verify(Base64.decode(sign, Base64.DEFAULT)); + return bverify; + + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "doCheck NoSuchAlgorithmException" + e); + } catch (InvalidKeySpecException e) { + Log.e(TAG, "doCheck InvalidKeySpecException" + e); + } catch (InvalidKeyException e) { + Log.e(TAG, "doCheck InvalidKeyException" + e); + } catch (SignatureException e) { + Log.e(TAG, "doCheck SignatureException" + e); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "doCheck UnsupportedEncodingException" + e); + } + return false; + } + + /** + * get the publicKey of the application + * During the encoding process, avoid storing the public key in clear text. + * @return publickey + */ + public static String getPublicKey(){ + return PUBLIC_KEY; + } + +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/Constants.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/Constants.java new file mode 100755 index 0000000000..fba4db210a --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/Constants.java @@ -0,0 +1,33 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +/** + * Constants Class. + * + * @since 2019/12/9 + */ +public class Constants { + + /** requestCode for pull up the pmsPay page */ + public static final int REQ_CODE_BUY_SUB = 4002; + public static final int REQ_CODE_BUY_INAPP = 4003; + + /** requestCode for pull up the login page for isEnvReady interface */ + public static final int REQ_CODE_LOGIN = 2001; + +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/ExceptionHandle.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/ExceptionHandle.java new file mode 100755 index 0000000000..06fa78b260 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/ExceptionHandle.java @@ -0,0 +1,103 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +import android.app.Activity; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import com.huawei.hms.iap.IapApiException; +import com.huawei.hms.iap.entity.OrderStatusCode; + +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; + +/** + * Handles the exception returned from the iap api. + * + * @since 2019/12/9 + */ +public class ExceptionHandle { + + protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(ExceptionHandle.class); + + /** + * The exception is solved. + */ + public static final int SOLVED = 0; + + /** + * Handles the exception returned from the iap api. + * @param activity The Activity to call the iap api. + * @param e The exception returned from the iap api. + * @return int + */ + public static int handle(@Nullable Activity activity, Exception e) { + + if (e instanceof IapApiException) { + IapApiException iapApiException = (IapApiException) e; + LOG.info("returnCode: " + iapApiException.getStatusCode()); + switch (iapApiException.getStatusCode()) { + case OrderStatusCode.ORDER_STATE_CANCEL: + showToast(activity, "Order has been canceled!"); + return SOLVED; + case OrderStatusCode.ORDER_STATE_PARAM_ERROR: + showToast(activity, "Order state param error!"); + return SOLVED; + case OrderStatusCode.ORDER_STATE_NET_ERROR: + showToast(activity, "Order state net error!"); + return SOLVED; + case OrderStatusCode.ORDER_VR_UNINSTALL_ERROR: + showToast(activity, "Order vr uninstall error!"); + return SOLVED; + case OrderStatusCode.ORDER_HWID_NOT_LOGIN: + IapRequestHelper.startResolutionForResult(activity, iapApiException.getStatus(), Constants.REQ_CODE_LOGIN); + return SOLVED; + case OrderStatusCode.ORDER_PRODUCT_OWNED: + showToast(activity, "Product already owned error!"); + return OrderStatusCode.ORDER_PRODUCT_OWNED; + case OrderStatusCode.ORDER_PRODUCT_NOT_OWNED: + showToast(activity, "Product not owned error!"); + return SOLVED; + case OrderStatusCode.ORDER_PRODUCT_CONSUMED: + showToast(activity, "Product consumed error!"); + return SOLVED; + case OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED: + showToast(activity, "Order account area not supported error!"); + return SOLVED; + case OrderStatusCode.ORDER_NOT_ACCEPT_AGREEMENT: + showToast(activity, "User does not agree the agreement"); + return SOLVED; + default: + // handle other error scenarios + showToast(activity, "Order unknown error!"); + return SOLVED; + } + } else { + showToast(activity, "External error"); + LOG.error(e.getMessage(), e); + return SOLVED; + } + } + + private static void showToast(@Nullable Activity activity, String s) { + if (AndroidUtils.isActivityNotDestroyed(activity)) { + Toast.makeText(activity, s, Toast.LENGTH_SHORT).show(); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/IapApiCallback.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/IapApiCallback.java new file mode 100755 index 0000000000..d8fb908093 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/IapApiCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +/** + * Used to callback the result from iap api. + * + * @since 2019/12/9 + */ +public interface IapApiCallback { + + /** + * The request is successful. + * @param result The result of a successful response. + */ + void onSuccess(T result); + + /** + * Callback fail. + * @param e An Exception from IAPSDK. + */ + void onFail(Exception e); +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/IapRequestHelper.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/IapRequestHelper.java new file mode 100755 index 0000000000..5c1b7838a5 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/IapRequestHelper.java @@ -0,0 +1,351 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +import android.app.Activity; +import android.content.IntentSender; +import android.text.TextUtils; +import android.util.Log; + +import com.huawei.hmf.tasks.OnFailureListener; +import com.huawei.hmf.tasks.OnSuccessListener; +import com.huawei.hmf.tasks.Task; +import com.huawei.hms.iap.Iap; +import com.huawei.hms.iap.IapApiException; +import com.huawei.hms.iap.IapClient; +import com.huawei.hms.iap.entity.ConsumeOwnedPurchaseReq; +import com.huawei.hms.iap.entity.ConsumeOwnedPurchaseResult; +import com.huawei.hms.iap.entity.IsEnvReadyResult; +import com.huawei.hms.iap.entity.OwnedPurchasesReq; +import com.huawei.hms.iap.entity.OwnedPurchasesResult; +import com.huawei.hms.iap.entity.ProductInfoReq; +import com.huawei.hms.iap.entity.ProductInfoResult; +import com.huawei.hms.iap.entity.PurchaseIntentReq; +import com.huawei.hms.iap.entity.PurchaseIntentResult; +import com.huawei.hms.iap.entity.StartIapActivityReq; +import com.huawei.hms.iap.entity.StartIapActivityResult; +import com.huawei.hms.support.api.client.Status; + +import java.util.List; + +/** + * The tool class of Iap interface. + * + * @since 2019/12/9 + */ +public class IapRequestHelper { + private final static String TAG = "IapRequestHelper"; + + /** + * Create a PurchaseIntentReq object. + * @param type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param productId ID of the in-app product to be paid. + * The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect. + * @return PurchaseIntentReq + */ + private static PurchaseIntentReq createPurchaseIntentReq(int type, String productId) { + PurchaseIntentReq req = new PurchaseIntentReq(); + req.setPriceType(type); + req.setProductId(productId); + req.setDeveloperPayload("testPurchase"); + return req; + } + + /** + * Create a ConsumeOwnedPurchaseReq object. + * @param purchaseToken which is generated by the Huawei payment server during product payment and returned to the app through InAppPurchaseData. + * The app transfers this parameter for the Huawei payment server to update the order status and then deliver the in-app product. + * @return ConsumeOwnedPurchaseReq + */ + private static ConsumeOwnedPurchaseReq createConsumeOwnedPurchaseReq(String purchaseToken) { + ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq(); + req.setPurchaseToken(purchaseToken); + req.setDeveloperChallenge("testConsume"); + return req; + } + + /** + * Create a OwnedPurchasesReq object. + * @param type type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param continuationToken A data location flag which returns from obtainOwnedPurchases api or obtainOwnedPurchaseRecord api. + * @return OwnedPurchasesReq + */ + private static OwnedPurchasesReq createOwnedPurchasesReq(int type, String continuationToken) { + OwnedPurchasesReq req = new OwnedPurchasesReq(); + req.setPriceType(type); + req.setContinuationToken(continuationToken); + return req; + } + + /** + * Create a ProductInfoReq object. + * @param type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param productIds ID list of products to be queried. Each product ID must exist and be unique in the current app. + * @return ProductInfoReq + */ + private static ProductInfoReq createProductInfoReq(int type, List productIds) { + ProductInfoReq req = new ProductInfoReq(); + req.setPriceType(type); + req.setProductIds(productIds); + return req; + } + + /** + * To check whether the country or region of the logged in HUAWEI ID is included in the countries or regions supported by HUAWEI IAP. + * @param mClient IapClient instance to call the isEnvReady API. + * @param callback IapApiCallback. + */ + public static void isEnvReady(IapClient mClient, final IapApiCallback callback) { + Log.i(TAG, "call isEnvReady"); + Task task = mClient.isEnvReady(); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(IsEnvReadyResult result) { + Log.i(TAG, "isEnvReady, success"); + callback.onSuccess(result); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "isEnvReady, fail"); + callback.onFail(e); + } + }); + } + + /** + * Obtain in-app product details configured in AppGallery Connect. + * @param iapClient IapClient instance to call the obtainProductInfo API. + * @param productIds ID list of products to be queried. Each product ID must exist and be unique in the current app. + * @param type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param callback IapApiCallback + */ + public static void obtainProductInfo(IapClient iapClient, final List productIds, int type, final IapApiCallback callback) { + Log.i(TAG, "call obtainProductInfo"); + + Task task = iapClient.obtainProductInfo(createProductInfoReq(type, productIds)); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(ProductInfoResult result) { + Log.i(TAG, "obtainProductInfo, success"); + callback.onSuccess(result); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "obtainProductInfo, fail"); + callback.onFail(e); + } + }); + } + + /** + * create orders for in-app products in the PMS + * @param iapClient IapClient instance to call the createPurchaseIntent API. + * @param productId ID of the in-app product to be paid. + * The in-app product ID is the product ID you set during in-app product configuration in AppGallery Connect. + * @param type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param callback IapApiCallback + */ + public static void createPurchaseIntent(final IapClient iapClient, String productId, int type, final IapApiCallback callback) { + Log.i(TAG, "call createPurchaseIntent"); + Task task = iapClient.createPurchaseIntent(createPurchaseIntentReq(type, productId)); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(PurchaseIntentResult result) { + Log.i(TAG, "createPurchaseIntent, success"); + callback.onSuccess(result); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "createPurchaseIntent, fail"); + callback.onFail(e); + + } + }); + } + + public static void createPurchaseIntent(final IapClient iapClient, String productId, int type, String payload, final IapApiCallback callback) { + Log.i(TAG, "call createPurchaseIntent"); + PurchaseIntentReq req = createPurchaseIntentReq(type, productId); + req.setDeveloperPayload(payload); + Task task = iapClient.createPurchaseIntent(req); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(PurchaseIntentResult result) { + Log.i(TAG, "createPurchaseIntent, success"); + callback.onSuccess(result); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "createPurchaseIntent, fail"); + callback.onFail(e); + + } + }); + } + + /** + * to start an activity. + * @param activity the activity to launch a new page. + * @param status This parameter contains the pendingIntent object of the payment page. + * @param reqCode Result code. + */ + public static void startResolutionForResult(Activity activity, Status status, int reqCode) { + if (status == null) { + Log.e(TAG, "status is null"); + return; + } + if (status.hasResolution()) { + try { + status.startResolutionForResult(activity, reqCode); + } catch (IntentSender.SendIntentException exp) { + Log.e(TAG, exp.getMessage()); + } + } else { + Log.e(TAG, "intent is null"); + } + } + + /** + * query information about all subscribed in-app products, including consumables, non-consumables, and auto-renewable subscriptions.
+ * If consumables are returned, the system needs to deliver them and calls the consumeOwnedPurchase API to consume the products. + * If non-consumables are returned, the in-app products do not need to be consumed. + * If subscriptions are returned, all existing subscription relationships of the user under the app are returned. + * @param mClient IapClient instance to call the obtainOwnedPurchases API. + * @param type In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription + * @param callback IapApiCallback + */ + public static void obtainOwnedPurchases(IapClient mClient, final int type, String continuationToken, final IapApiCallback callback) { + Log.i(TAG, "call obtainOwnedPurchases"); + Task task = mClient.obtainOwnedPurchases(IapRequestHelper.createOwnedPurchasesReq(type, continuationToken)); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(OwnedPurchasesResult result) { + Log.i(TAG, "obtainOwnedPurchases, success"); + callback.onSuccess(result); + + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "obtainOwnedPurchases, fail"); + callback.onFail(e); + } + }); + + } + + /** + * obtain the historical consumption information about a consumable in-app product or all subscription receipts of a subscription. + * @param iapClient IapClient instance to call the obtainOwnedPurchaseRecord API. + * @param priceType In-app product type. + * The value contains: 0: consumable 1: non-consumable 2 auto-renewable subscription. + * @param continuationToken Data locating flag for supporting query in pagination mode. + * @param callback IapApiCallback + */ + public static void obtainOwnedPurchaseRecord(IapClient iapClient, int priceType, String continuationToken, final IapApiCallback callback) { + Log.i(TAG, "call obtainOwnedPurchaseRecord"); + Task task = iapClient.obtainOwnedPurchaseRecord(createOwnedPurchasesReq(priceType, continuationToken)); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(OwnedPurchasesResult result) { + Log.i(TAG, "obtainOwnedPurchaseRecord, success"); + callback.onSuccess(result); + + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + Log.e(TAG, "obtainOwnedPurchaseRecord, fail"); + callback.onFail(e); + } + }); + } + + /** + * Consume all the unconsumed purchases with priceType 0. + * @param iapClient IapClient instance to call the consumeOwnedPurchase API. + * @param purchaseToken which is generated by the Huawei payment server during product payment and returned to the app through InAppPurchaseData. + */ + public static void consumeOwnedPurchase(IapClient iapClient, String purchaseToken) { + Log.i(TAG, "call consumeOwnedPurchase"); + Task task = iapClient.consumeOwnedPurchase(createConsumeOwnedPurchaseReq(purchaseToken)); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(ConsumeOwnedPurchaseResult result) { + // Consume success. + Log.i(TAG, "consumeOwnedPurchase success"); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + if (e instanceof IapApiException) { + IapApiException apiException = (IapApiException)e; + int returnCode = apiException.getStatusCode(); + Log.e(TAG, "consumeOwnedPurchase fail, IapApiException returnCode: " + returnCode); + } else { + // Other external errors + Log.e(TAG, e.getMessage()); + } + + } + }); + + } + + /** + * link to subscription manager page + * @param activity activity + * @param productId the productId of the subscription product + */ + public static void showSubscription(final Activity activity, String productId) { + StartIapActivityReq req = new StartIapActivityReq(); + if (TextUtils.isEmpty(productId)) { + req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_MANAGER_ACTIVITY); + } else { + req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_EDIT_ACTIVITY); + req.setSubscribeProductId(productId); + } + + IapClient iapClient = Iap.getIapClient(activity); + Task task = iapClient.startIapActivity(req); + + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StartIapActivityResult result) { + if(result != null) { + result.startActivity(activity); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + ExceptionHandle.handle(activity, e); + } + }); + } + +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index a7e8589f33..61de5125c0 100644 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -1,393 +1,229 @@ package net.osmand.plus.inapp; import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.huawei.hms.iap.Iap; +import com.huawei.hms.iap.IapClient; +import com.huawei.hms.iap.entity.InAppPurchaseData; +import com.huawei.hms.iap.entity.IsEnvReadyResult; +import com.huawei.hms.iap.entity.OrderStatusCode; +import com.huawei.hms.iap.entity.OwnedPurchasesResult; +import com.huawei.hms.iap.entity.ProductInfo; +import com.huawei.hms.iap.entity.ProductInfoResult; +import com.huawei.hms.iap.entity.PurchaseIntentResult; +import com.huawei.hms.iap.entity.PurchaseResultInfo; + import net.osmand.AndroidUtils; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.inapp.util.BillingManager; +import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; +import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; +import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo; import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference; import net.osmand.util.Algorithms; import java.lang.ref.WeakReference; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { - // The helper object - private BillingManager billingManager; - private List skuDetailsList; + private boolean envReady = false; + private boolean purchaseSupported = false; + private List productInfos; + private OwnedPurchasesResult ownedSubscriptions; + private List ownedInApps = new ArrayList<>(); public InAppPurchaseHelperImpl(OsmandApplication ctx) { super(ctx); + purchases = new InAppPurchasesImpl(ctx); } - protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { - billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { - - @Override - public void onBillingClientSetupFinished() { - logDebug("Setup finished."); - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; + @Override + public void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback) { + if (envReady) { + if (callback != null) { + if (purchaseSupported) { + callback.onSuccess(); + } else { + callback.onFail(); } - - if (!billingManager.isServiceConnected()) { - // Oh noes, there was a problem. - //complain("Problem setting up in-app billing: " + result); - notifyError(taskType, billingManager.getBillingClientResponseMessage()); - stop(true); - return; - } - - processingTask = !runnable.run(InAppPurchaseHelperImpl.this); } + } else { + // Initiating an isEnvReady request when entering the app. + // Check if the account service country supports IAP. + IapClient mClient = Iap.getIapClient(activity); + final WeakReference activityRef = new WeakReference<>(activity); + IapRequestHelper.isEnvReady(mClient, new IapApiCallback() { - @Override - public void onConsumeFinished(String token, BillingResult billingResult) { - } - - @Override - public void onPurchasesUpdated(final List purchases) { - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; + private void onReady(boolean succeed) { + logDebug("Setup finished."); + envReady = true; + purchaseSupported = succeed; + if (callback != null) { + if (succeed) { + callback.onSuccess(); + } else { + callback.onFail(); + } + } } - if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { - List skuInApps = new ArrayList<>(); - for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { - skuInApps.add(purchase.getSku()); - } - for (Purchase p : purchases) { - skuInApps.add(p.getSku()); - } - billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { - logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } + @Override + public void onSuccess(IsEnvReadyResult result) { + onReady(true); + } - List skuSubscriptions = new ArrayList<>(); - for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { - skuSubscriptions.add(subscription.getSku()); - } - for (Purchase p : purchases) { - skuSubscriptions.add(p.getSku()); - } + @Override + public void onFail(Exception e) { + onReady(false); + LOG.error("isEnvReady fail, " + e.getMessage(), e); + ExceptionHandle.handle(activityRef.get(), e); + } + }); + } + } - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand command) { + if (envReady) { + command.run(this); + } else { + command.commandDone(); + } + } - billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { + private InAppCommand getPurchaseInAppCommand(@NonNull final Activity activity, @NonNull final String productId) throws UnsupportedOperationException { + return new InAppCommand() { + @Override + public void run(InAppPurchaseHelper helper) { + try { + ProductInfo productInfo = getProductInfo(productId); + IapRequestHelper.createPurchaseIntent(getIapClient(), productInfo.getProductId(), + IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { - logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; + public void onSuccess(PurchaseIntentResult result) { + if (result == null) { + logError("result is null"); + } else { + // you should pull up the page to complete the payment process + IapRequestHelper.startResolutionForResult(activity, result.getStatus(), Constants.REQ_CODE_BUY_INAPP); } + commandDone(); + } - List skuDetailsList = new ArrayList<>(skuDetailsListInApps); - skuDetailsList.addAll(skuDetailsListSubscriptions); - InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList; - - mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle(activity, e); + if (errorCode != ExceptionHandle.SOLVED) { + logDebug("createPurchaseIntent, returnCode: " + errorCode); + if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) { + logError("already own this product"); + } else { + logError("unknown error"); + } + } + commandDone(); } }); - } - }); - } - for (Purchase purchase : purchases) { - if (!purchase.isAcknowledged()) { - onPurchaseFinished(purchase); - } + } catch (Exception e) { + complain("Cannot launch full version purchase!"); + logError("purchaseFullVersion Error", e); + stop(true); } } - - @Override - public void onPurchaseCanceled() { - stop(true); - } - }); + }; } @Override - public void purchaseFullVersion(Activity activity) throws UnsupportedOperationException { - throw new UnsupportedOperationException(); + public void purchaseFullVersion(@NonNull final Activity activity) throws UnsupportedOperationException { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); + exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, getPurchaseInAppCommand(activity, "")); } @Override - public void purchaseDepthContours(Activity activity) throws UnsupportedOperationException { - throw new UnsupportedOperationException(); + public void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); + exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, getPurchaseInAppCommand(activity, "")); } @Nullable - private SkuDetails getSkuDetails(@NonNull String sku) { - List skuDetailsList = this.skuDetailsList; - if (skuDetailsList != null) { - for (SkuDetails details : skuDetailsList) { - if (details.getSku().equals(sku)) { - return details; + private ProductInfo getProductInfo(@NonNull String productId) { + List productInfos = this.productInfos; + if (productInfos != null) { + for (ProductInfo info : productInfos) { + if (info.getProductId().equals(productId)) { + return info; } } } return null; } - private boolean hasDetails(@NonNull String sku) { - return getSkuDetails(sku) != null; + private boolean hasDetails(@NonNull String productId) { + return getProductInfo(productId) != null; } @Nullable - private Purchase getPurchase(@NonNull String sku) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - List purchases = billingManager.getPurchases(); - if (purchases != null) { - for (Purchase p : purchases) { - if (p.getSku().equals(sku)) { - return p; - } + private InAppPurchaseData getPurchaseData(@NonNull String productId) { + InAppPurchaseData data = SubscriptionUtils.getPurchaseData(ownedSubscriptions, productId); + if (data == null) { + for (OwnedPurchasesResult result : ownedInApps) { + data = InAppUtils.getPurchaseData(result, productId); + if (data != null) { + break; } } } - return null; + return data; } - // Listener that's called when we finish querying the items and subscriptions we own - private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { - - @NonNull - private List getAllOwnedSubscriptionSkus() { - List result = new ArrayList<>(); - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - for (Purchase p : billingManager.getPurchases()) { - if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { - result.add(p.getSku()); - } - } - } - return result; - } - - @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { - - logDebug("Query sku details finished."); - - // Have we been disposed of in the meantime? If so, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - - // Is it a failure? - if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { - logError("Failed to query inventory: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - logDebug("Query sku details was successful."); - - /* - * Check for items we own. Notice that for each purchase, we check - * the developer payload to see if it's correct! See - * verifyDeveloperPayload(). - */ - - List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); - for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { - if (hasDetails(s.getSku())) { - Purchase purchase = getPurchase(s.getSku()); - SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); - if (liveUpdatesDetails != null) { - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - allOwnedSubscriptionSkus.remove(s.getSku()); - } - } - for (String sku : allOwnedSubscriptionSkus) { - Purchase purchase = getPurchase(sku); - SkuDetails liveUpdatesDetails = getSkuDetails(sku); - if (liveUpdatesDetails != null) { - InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); - if (s == null) { - s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); - } - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - } - - InAppPurchases.InAppPurchase fullVersion = getFullVersion(); - if (hasDetails(fullVersion.getSku())) { - Purchase purchase = getPurchase(fullVersion.getSku()); - SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); - if (fullPriceDetails != null) { - fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); - } - } - - InAppPurchases.InAppPurchase depthContours = getDepthContours(); - if (hasDetails(depthContours.getSku())) { - Purchase purchase = getPurchase(depthContours.getSku()); - SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); - if (depthContoursDetails != null) { - fetchInAppPurchase(depthContours, depthContoursDetails, purchase); - } - } - - InAppPurchases.InAppPurchase contourLines = getContourLines(); - if (hasDetails(contourLines.getSku())) { - Purchase purchase = getPurchase(contourLines.getSku()); - SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); - if (contourLinesDetails != null) { - fetchInAppPurchase(contourLines, contourLinesDetails, purchase); - } - } - - Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); - boolean fullVersionPurchased = fullVersionPurchase != null; - if (fullVersionPurchased) { - ctx.getSettings().FULL_VERSION_PURCHASED.set(true); - } - - Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); - boolean depthContoursPurchased = depthContoursPurchase != null; - if (depthContoursPurchased) { - ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); - } - - // Do we have the live updates? - boolean subscribedToLiveUpdates = false; - List liveUpdatesPurchases = new ArrayList<>(); - for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { - Purchase purchase = getPurchase(p.getSku()); - if (purchase != null) { - liveUpdatesPurchases.add(purchase); - if (!subscribedToLiveUpdates) { - subscribedToLiveUpdates = true; - } - } - } - OsmandSettings.OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; - if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { - if (subscriptionCancelledTime.get() == 0) { - subscriptionCancelledTime.set(System.currentTimeMillis()); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); - } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); - if (!isDepthContoursPurchased(ctx)) { - ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); - } - } - } else if (subscribedToLiveUpdates) { - subscriptionCancelledTime.set(0L); - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); - } - - lastValidationCheckTime = System.currentTimeMillis(); - logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") - + " live updates purchased."); - - OsmandSettings settings = ctx.getSettings(); - settings.INAPPS_READ.set(true); - - List tokensToSend = new ArrayList<>(); - if (liveUpdatesPurchases.size() > 0) { - List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); - for (Purchase purchase : liveUpdatesPurchases) { - if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) - && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { - String payload = purchase.getDeveloperPayload(); - if (!Algorithms.isEmpty(payload)) { - String[] arr = payload.split(" "); - if (arr.length > 0) { - settings.BILLING_USER_ID.set(arr[0]); - } - if (arr.length > 1) { - token = arr[1]; - settings.BILLING_USER_TOKEN.set(token); - } - } - } - if (!tokensSent.contains(purchase.getSku())) { - tokensToSend.add(purchase); - } - } - } - List purchaseInfoList = new ArrayList<>(); - for (Purchase purchase : tokensToSend) { - purchaseInfoList.add(getPurchaseInfo(purchase)); - } - onSkuDetailsResponseDone(purchaseInfoList); - } - }; - - private PurchaseInfo getPurchaseInfo(Purchase purchase) { - return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); + private PurchaseInfo getPurchaseInfo(InAppPurchaseData purchase) { + return new PurchaseInfo(purchase.getProductId(), purchase.getOrderID(), purchase.getPurchaseToken()); } - private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { - if (purchase != null) { - inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); - inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); + private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull ProductInfo productInfo, @Nullable InAppPurchaseData purchaseData) { + if (purchaseData != null) { + inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseTime(purchaseData.getPurchaseTime()); } else { - inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED); } - inAppPurchase.setPrice(skuDetails.getPrice()); - inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); - if (skuDetails.getPriceAmountMicros() > 0) { - inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); + inAppPurchase.setPrice(productInfo.getPrice()); + inAppPurchase.setPriceCurrencyCode(productInfo.getCurrency()); + if (productInfo.getMicrosPrice() > 0) { + inAppPurchase.setPriceValue(productInfo.getMicrosPrice() / 1000000d); } - String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); + String subscriptionPeriod = productInfo.getSubPeriod(); if (!Algorithms.isEmpty(subscriptionPeriod)) { - if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + if (inAppPurchase instanceof InAppSubscription) { try { - ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + ((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); } catch (ParseException e) { LOG.error(e); } } } - if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { - String introductoryPrice = skuDetails.getIntroductoryPrice(); - String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); - String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); - long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); + if (inAppPurchase instanceof InAppSubscription) { + String introductoryPrice = productInfo.getSubSpecialPrice(); + String introductoryPricePeriod = productInfo.getSubSpecialPeriod(); + int introductoryPriceCycles = productInfo.getSubSpecialPeriodCycles(); + long introductoryPriceAmountMicros = productInfo.getSubSpecialPriceMicros(); if (!Algorithms.isEmpty(introductoryPrice)) { - InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + InAppSubscription s = (InAppSubscription) inAppPurchase; try { - s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, - introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); + s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice, + introductoryPriceAmountMicros, introductoryPricePeriod, String.valueOf(introductoryPriceCycles))); } catch (ParseException e) { LOG.error(e); } @@ -395,22 +231,45 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { - return new InAppRunnable() { + protected InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppCommand() { @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { try { Activity a = activity.get(); - SkuDetails skuDetails = getSkuDetails(sku); - if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.setPayload(payload); - billingManager.initiatePurchaseFlow(a, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; + ProductInfo productInfo = getProductInfo(sku); + if (AndroidUtils.isActivityNotDestroyed(a) && productInfo != null) { + IapRequestHelper.createPurchaseIntent(getIapClient(), sku, + IapClient.PriceType.IN_APP_SUBSCRIPTION, payload, new IapApiCallback() { + @Override + public void onSuccess(PurchaseIntentResult result) { + if (result == null) { + logError("GetBuyIntentResult is null"); + } else { + Activity a = activity.get(); + if (AndroidUtils.isActivityNotDestroyed(a)) { + IapRequestHelper.startResolutionForResult(a, result.getStatus(), Constants.REQ_CODE_BUY_SUB); + } else { + logError("startResolutionForResult on destroyed activity"); + } + } + commandDone(); + } + + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle(activity.get(), e); + if (ExceptionHandle.SOLVED != errorCode) { + logError("createPurchaseIntent, returnCode: " + errorCode); + if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) { + logError("already own this product"); + } else { + logError("unknown error"); + } + } + commandDone(); + } + }); } else { stop(true); } @@ -418,58 +277,362 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { logError("launchPurchaseFlow Error", e); stop(true); } - return true; } }; } - protected InAppRunnable getRequestInventoryCommand() { - return new InAppRunnable() { + @Override + protected InAppCommand getRequestInventoryCommand() { + return new InAppCommand() { + @Override - public boolean run(InAppPurchaseHelper helper) { + public void run(InAppPurchaseHelper helper) { logDebug("Setup successful. Querying inventory."); try { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.queryPurchases(); + productInfos = new ArrayList<>(); + if (uiActivity != null) { + obtainOwnedSubscriptions(); } else { - throw new IllegalStateException("BillingManager disposed"); + commandDone(); } - return false; } catch (Exception e) { logError("queryInventoryAsync Error", e); notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); stop(true); + commandDone(); } - return true; + } + + private void obtainOwnedSubscriptions() { + if (uiActivity != null) { + IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_SUBSCRIPTION, + null, new IapApiCallback() { + @Override + public void onSuccess(OwnedPurchasesResult result) { + ownedSubscriptions = result; + obtainOwnedInApps(null); + } + + @Override + public void onFail(Exception e) { + logError("obtainOwnedSubscriptions exception", e); + ExceptionHandle.handle((Activity) uiActivity, e); + commandDone(); + } + }); + } else { + commandDone(); + } + } + + private void obtainOwnedInApps(final String continuationToken) { + // Query users' purchased non-consumable products. + IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_NONCONSUMABLE, + continuationToken, new IapApiCallback() { + @Override + public void onSuccess(OwnedPurchasesResult result) { + ownedInApps.add(result); + if (result != null && !TextUtils.isEmpty(result.getContinuationToken())) { + obtainOwnedInApps(result.getContinuationToken()); + } else { + obtainSubscriptionsInfo(); + } + } + + @Override + public void onFail(Exception e) { + logError("obtainOwnedInApps exception", e); + ExceptionHandle.handle((Activity) uiActivity, e); + commandDone(); + } + }); + + } + + private void obtainSubscriptionsInfo() { + Set productIds = new HashSet<>(); + List subscriptions = purchases.getLiveUpdates().getAllSubscriptions(); + for (InAppSubscription s : subscriptions) { + productIds.add(s.getSku()); + } + productIds.addAll(ownedSubscriptions.getItemList()); + IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), + IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback() { + @Override + public void onSuccess(final ProductInfoResult result) { + if (result == null) { + logError("obtainSubscriptionsInfo: ProductInfoResult is null"); + commandDone(); + return; + } + productInfos.addAll(result.getProductInfoList()); + obtainInAppsInfo(); + } + + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); + if (ExceptionHandle.SOLVED != errorCode) { + LOG.error("Unknown error"); + } + commandDone(); + } + }); + } + + private void obtainInAppsInfo() { + Set productIds = new HashSet<>(); + for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + productIds.add(purchase.getSku()); + } + for (OwnedPurchasesResult result : ownedInApps) { + productIds.addAll(result.getItemList()); + } + IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), + IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { + @Override + public void onSuccess(ProductInfoResult result) { + if (result == null || result.getProductInfoList() == null) { + logError("obtainInAppsInfo: ProductInfoResult is null"); + commandDone(); + return; + } + productInfos.addAll(result.getProductInfoList()); + + processInventory(); + commandDone(); + } + + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); + if (ExceptionHandle.SOLVED != errorCode) { + LOG.error("Unknown error"); + } + commandDone(); + } + }); + } + + private void processInventory() { + logDebug("Query sku details was successful."); + + /* + * Check for items we own. Notice that for each purchase, we check + * the developer payload to see if it's correct! + */ + + List allOwnedSubscriptionSkus = ownedSubscriptions.getItemList(); + for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + if (hasDetails(s.getSku())) { + InAppPurchaseData purchaseData = getPurchaseData(s.getSku()); + ProductInfo liveUpdatesInfo = getProductInfo(s.getSku()); + if (liveUpdatesInfo != null) { + fetchInAppPurchase(s, liveUpdatesInfo, purchaseData); + } + allOwnedSubscriptionSkus.remove(s.getSku()); + } + } + for (String sku : allOwnedSubscriptionSkus) { + InAppPurchaseData purchaseData = getPurchaseData(sku); + ProductInfo liveUpdatesInfo = getProductInfo(sku); + if (liveUpdatesInfo != null) { + InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + if (s == null) { + s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesInfo); + } + fetchInAppPurchase(s, liveUpdatesInfo, purchaseData); + } + } + + InAppPurchase fullVersion = getFullVersion(); + if (hasDetails(fullVersion.getSku())) { + InAppPurchaseData purchaseData = getPurchaseData(fullVersion.getSku()); + ProductInfo fullPriceDetails = getProductInfo(fullVersion.getSku()); + if (fullPriceDetails != null) { + fetchInAppPurchase(fullVersion, fullPriceDetails, purchaseData); + } + } + + InAppPurchase depthContours = getDepthContours(); + if (hasDetails(depthContours.getSku())) { + InAppPurchaseData purchaseData = getPurchaseData(depthContours.getSku()); + ProductInfo depthContoursDetails = getProductInfo(depthContours.getSku()); + if (depthContoursDetails != null) { + fetchInAppPurchase(depthContours, depthContoursDetails, purchaseData); + } + } + + InAppPurchase contourLines = getContourLines(); + if (hasDetails(contourLines.getSku())) { + InAppPurchaseData purchaseData = getPurchaseData(contourLines.getSku()); + ProductInfo contourLinesDetails = getProductInfo(contourLines.getSku()); + if (contourLinesDetails != null) { + fetchInAppPurchase(contourLines, contourLinesDetails, purchaseData); + } + } + + InAppPurchaseData fullVersionPurchase = getPurchaseData(fullVersion.getSku()); + boolean fullVersionPurchased = fullVersionPurchase != null; + if (fullVersionPurchased) { + ctx.getSettings().FULL_VERSION_PURCHASED.set(true); + } + + InAppPurchaseData depthContoursPurchase = getPurchaseData(depthContours.getSku()); + boolean depthContoursPurchased = depthContoursPurchase != null; + if (depthContoursPurchased) { + ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); + } + + // Do we have the live updates? + boolean subscribedToLiveUpdates = false; + List liveUpdatesPurchases = new ArrayList<>(); + for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + InAppPurchaseData purchaseData = getPurchaseData(p.getSku()); + if (purchaseData != null) { + liveUpdatesPurchases.add(purchaseData); + if (!subscribedToLiveUpdates) { + subscribedToLiveUpdates = true; + } + } + } + OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; + if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { + if (subscriptionCancelledTime.get() == 0) { + subscriptionCancelledTime.set(System.currentTimeMillis()); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); + } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); + if (!isDepthContoursPurchased(ctx)) { + ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); + } + } + } else if (subscribedToLiveUpdates) { + subscriptionCancelledTime.set(0L); + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); + } + + lastValidationCheckTime = System.currentTimeMillis(); + logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + + " live updates purchased."); + + OsmandSettings settings = ctx.getSettings(); + settings.INAPPS_READ.set(true); + + List tokensToSend = new ArrayList<>(); + if (liveUpdatesPurchases.size() > 0) { + List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); + for (InAppPurchaseData purchase : liveUpdatesPurchases) { + if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) + && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { + String payload = purchase.getDeveloperPayload(); + if (!Algorithms.isEmpty(payload)) { + String[] arr = payload.split(" "); + if (arr.length > 0) { + settings.BILLING_USER_ID.set(arr[0]); + } + if (arr.length > 1) { + token = arr[1]; + settings.BILLING_USER_TOKEN.set(token); + } + } + } + if (!tokensSent.contains(purchase.getProductId())) { + tokensToSend.add(purchase); + } + } + } + List purchaseInfoList = new ArrayList<>(); + for (InAppPurchaseData purchase : tokensToSend) { + purchaseInfoList.add(getPurchaseInfo(purchase)); + } + onSkuDetailsResponseDone(purchaseInfoList); } }; } + private IapClient getIapClient() { + return Iap.getIapClient((Activity) uiActivity); + } + // Call when a purchase is finished - private void onPurchaseFinished(Purchase purchase) { - logDebug("Purchase finished: " + purchase); - - // if we were disposed of in the meantime, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - + private void onPurchaseFinished(InAppPurchaseData purchase) { + logDebug("Purchase finished: " + purchase.getProductId()); onPurchaseDone(getPurchaseInfo(purchase)); } @Override protected boolean isBillingManagerExists() { - return getBillingManager() != null; + return false; } @Override protected void destroyBillingManager() { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.destroy(); - this.billingManager = null; + // non implemented + } + + @Override + public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, Intent data) { + if (requestCode == Constants.REQ_CODE_BUY_SUB) { + boolean succeed = false; + if (resultCode == Activity.RESULT_OK) { + PurchaseResultInfo result = SubscriptionUtils.getPurchaseResult(activity, data); + if (result != null) { + if (OrderStatusCode.ORDER_STATE_SUCCESS == result.getReturnCode()) { + InAppPurchaseData purchaseData = SubscriptionUtils.getInAppPurchaseData(null, + result.getInAppPurchaseData(), result.getInAppDataSignature()); + if (purchaseData != null) { + onPurchaseFinished(purchaseData); + succeed = true; + } else { + logDebug("Purchase failed"); + } + } else if (OrderStatusCode.ORDER_STATE_CANCEL == result.getReturnCode()) { + logDebug("Purchase cancelled"); + } + } else { + logDebug("Purchase failed"); + } + } else { + logDebug("Purchase cancelled"); + } + if (!succeed) { + stop(true); + } + return true; + } else if (requestCode == Constants.REQ_CODE_BUY_INAPP) { + boolean succeed = false; + if (data == null) { + logDebug("data is null"); + } else { + PurchaseResultInfo buyResultInfo = Iap.getIapClient(activity).parsePurchaseResultInfoFromIntent(data); + switch (buyResultInfo.getReturnCode()) { + case OrderStatusCode.ORDER_STATE_CANCEL: + logDebug("Order has been canceled"); + break; + case OrderStatusCode.ORDER_PRODUCT_OWNED: + logDebug("Product already owned"); + break; + case OrderStatusCode.ORDER_STATE_SUCCESS: + InAppPurchaseData purchaseData = InAppUtils.getInAppPurchaseData(null, + buyResultInfo.getInAppPurchaseData(), buyResultInfo.getInAppDataSignature()); + if (purchaseData != null) { + onPurchaseFinished(purchaseData); + succeed = true; + } else { + logDebug("Purchase failed"); + } + break; + default: + break; + } + } + if (!succeed) { + stop(true); + } + return true; } + return false; } } diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java new file mode 100644 index 0000000000..49ff72cd3b --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java @@ -0,0 +1,184 @@ +package net.osmand.plus.inapp; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; + +public class InAppPurchasesImpl extends InAppPurchases { + + private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion(); + private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree(); + private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree(); + + + private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{ + new InAppPurchaseLiveUpdatesMonthlyFree(), + new InAppPurchaseLiveUpdates3MonthsFree(), + new InAppPurchaseLiveUpdatesAnnualFree() + }; + + public InAppPurchasesImpl(OsmandApplication ctx) { + super(ctx); + fullVersion = FULL_VERSION; + depthContours = DEPTH_CONTOURS_FREE; + contourLines = CONTOUR_LINES_FREE; + liveUpdates = new LiveUpdatesInAppPurchasesFree(); + inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines }; + } + + @Override + public boolean isFullVersion(String sku) { + return FULL_VERSION.getSku().equals(sku); + } + + @Override + public boolean isDepthContours(String sku) { + return DEPTH_CONTOURS_FREE.getSku().equals(sku); + } + + @Override + public boolean isContourLines(String sku) { + return CONTOUR_LINES_FREE.getSku().equals(sku); + } + + @Override + public boolean isLiveUpdates(String sku) { + for (InAppPurchase p : LIVE_UPDATES_FREE) { + if (p.getSku().equals(sku)) { + return true; + } + } + return false; + } + + private static class InAppPurchaseFullVersion extends InAppPurchase { + + private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; + + InAppPurchaseFullVersion() { + super(SKU_FULL_VERSION_PRICE); + } + + @Override + public String getDefaultPrice(Context ctx) { + return ctx.getString(R.string.full_version_price); + } + } + + private static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours { + + private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth"; + + InAppPurchaseDepthContoursFree() { + super(SKU_DEPTH_CONTOURS_FREE); + } + } + + private static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines { + + private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines"; + + InAppPurchaseContourLinesFree() { + super(SKU_CONTOUR_LINES_FREE); + } + } + + private static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly { + + private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.test.monthly"; + + InAppPurchaseLiveUpdatesMonthlyFree() { + super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months { + + private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.test.3months"; + + InAppPurchaseLiveUpdates3MonthsFree() { + super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null; + } + } + + private static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual { + + private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.test.annual"; + + InAppPurchaseLiveUpdatesAnnualFree() { + super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null; + } + } + + public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription { + + private ProductInfo info; + + InAppPurchaseLiveUpdatesOldSubscription(@NonNull ProductInfo info) { + super(info.getProductId(), true); + this.info = info; + } + + @Override + public String getDefaultPrice(Context ctx) { + return ""; + } + + @Override + public CharSequence getTitle(Context ctx) { + return info.getProductName(); + } + + @Override + public CharSequence getDescription(@NonNull Context ctx) { + return info.getProductDesc(); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return null; + } + } + + private static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList { + + public LiveUpdatesInAppPurchasesFree() { + super(LIVE_UPDATES_FREE); + } + } +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppUtils.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppUtils.java new file mode 100644 index 0000000000..445727de96 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppUtils.java @@ -0,0 +1,49 @@ +package net.osmand.plus.inapp; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.huawei.hms.iap.entity.InAppPurchaseData; +import com.huawei.hms.iap.entity.OwnedPurchasesResult; + +import org.json.JSONException; + +public class InAppUtils { + private static final String TAG = "InAppUtils"; + + @Nullable + public static InAppPurchaseData getPurchaseData(OwnedPurchasesResult result, String productId) { + if (result == null || result.getInAppPurchaseDataList() == null) { + Log.i(TAG, "result is null"); + return null; + } + int index = result.getItemList().indexOf(productId); + if (index != -1) { + String data = result.getInAppPurchaseDataList().get(index); + String signature = result.getInAppSignature().get(index); + return getInAppPurchaseData(productId, data, signature); + } + return null; + } + + @Nullable + public static InAppPurchaseData getInAppPurchaseData(@Nullable String productId, @NonNull String data, @NonNull String signature) { + if (CipherUtil.doCheck(data, signature, CipherUtil.getPublicKey())) { + try { + InAppPurchaseData purchaseData = new InAppPurchaseData(data); + if (purchaseData.getPurchaseState() == InAppPurchaseData.PurchaseState.PURCHASED) { + if (productId == null || productId.equals(purchaseData.getProductId())) { + return purchaseData; + } + } + } catch (JSONException e) { + Log.e(TAG, "delivery: " + e.getMessage()); + } + } else { + Log.e(TAG, "delivery: verify signature error"); + } + return null; + } +} \ No newline at end of file diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java new file mode 100755 index 0000000000..1dffffc252 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java @@ -0,0 +1,139 @@ +/** + * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.osmand.plus.inapp; + +import android.app.Activity; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.huawei.hms.iap.Iap; +import com.huawei.hms.iap.entity.InAppPurchaseData; +import com.huawei.hms.iap.entity.OrderStatusCode; +import com.huawei.hms.iap.entity.OwnedPurchasesResult; +import com.huawei.hms.iap.entity.PurchaseResultInfo; + +import org.json.JSONException; + +import java.util.List; + +/** + * Util for Subscription function. + * + * @since 2019/12/9 + */ +public class SubscriptionUtils { + private static final String TAG = "SubscriptionUtils"; + + /** + * Decide whether to offer subscription service + * + * @param result the OwnedPurchasesResult from IapClient.obtainOwnedPurchases + * @param productId subscription product id + * @return decision result + */ + @Nullable + public static InAppPurchaseData getPurchaseData(OwnedPurchasesResult result, String productId) { + if (null == result) { + Log.e(TAG, "OwnedPurchasesResult is null"); + return null; + } + List dataList = result.getInAppPurchaseDataList(); + List signatureList = result.getInAppSignature(); + for (int i = 0; i < dataList.size(); i++) { + String data = dataList.get(i); + String signature = signatureList.get(i); + InAppPurchaseData purchaseData = getInAppPurchaseData(productId, data, signature); + if (purchaseData != null) { + return purchaseData; + } + } + return null; + } + + @Nullable + public static InAppPurchaseData getInAppPurchaseData(@Nullable String productId, @NonNull String data, @NonNull String signature) { + try { + InAppPurchaseData purchaseData = new InAppPurchaseData(data); + if (productId == null || productId.equals(purchaseData.getProductId())) { + boolean credible = CipherUtil.doCheck(data, signature, CipherUtil.getPublicKey()); + if (credible) { + return purchaseData.isSubValid() ? purchaseData : null; + } else { + Log.e(TAG, "check the data signature fail"); + return null; + } + } + } catch (JSONException e) { + Log.e(TAG, "parse InAppPurchaseData JSONException", e); + return null; + } + return null; + } + + /** + * Parse PurchaseResult data from intent + * + * @param activity Activity + * @param data the intent from onActivityResult + * @return PurchaseResultInfo + */ + public static PurchaseResultInfo getPurchaseResult(Activity activity, Intent data) { + PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(activity).parsePurchaseResultInfoFromIntent(data); + if (null == purchaseResultInfo) { + Log.e(TAG, "PurchaseResultInfo is null"); + } else { + int returnCode = purchaseResultInfo.getReturnCode(); + String errMsg = purchaseResultInfo.getErrMsg(); + switch (returnCode) { + case OrderStatusCode.ORDER_PRODUCT_OWNED: + Log.w(TAG, "you have owned this product"); + break; + case OrderStatusCode.ORDER_STATE_SUCCESS: + boolean credible = CipherUtil.doCheck(purchaseResultInfo.getInAppPurchaseData(), purchaseResultInfo.getInAppDataSignature(), CipherUtil + .getPublicKey()); + if (credible) { + try { + InAppPurchaseData inAppPurchaseData = new InAppPurchaseData(purchaseResultInfo.getInAppPurchaseData()); + if (!inAppPurchaseData.isSubValid()) { + return getFailedPurchaseResultInfo(); + } + } catch (JSONException e) { + Log.e(TAG, "parse InAppPurchaseData JSONException", e); + return getFailedPurchaseResultInfo(); + } + } else { + Log.e(TAG, "check the data signature fail"); + } + return getFailedPurchaseResultInfo(); + + default: + Log.e(TAG, "returnCode: " + returnCode + " , errMsg: " + errMsg); + break; + } + } + return purchaseResultInfo; + } + + private static PurchaseResultInfo getFailedPurchaseResultInfo() { + PurchaseResultInfo info = new PurchaseResultInfo(); + info.setReturnCode(OrderStatusCode.ORDER_STATE_FAILED); + return info; + } +} diff --git a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java index cb91f7d167..3929307cb9 100644 --- a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java @@ -5,7 +5,6 @@ import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; -import android.os.Bundle; import android.widget.Toast; import androidx.annotation.NonNull; @@ -14,12 +13,14 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import net.osmand.AndroidUtils; import net.osmand.PlatformUtil; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.inapp.InAppPurchaseHelper; +import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseInitCallback; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType; import net.osmand.plus.liveupdates.OsmLiveRestartBottomSheetDialogFragment; @@ -27,6 +28,7 @@ import net.osmand.plus.srtmplugin.SRTMPlugin; import org.apache.commons.logging.Log; +import java.lang.ref.WeakReference; import java.util.List; @SuppressLint("Registered") @@ -34,14 +36,7 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In private static final Log LOG = PlatformUtil.getLog(OsmandInAppPurchaseActivity.class); private InAppPurchaseHelper purchaseHelper; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (isInAppPurchaseAllowed() && isInAppPurchaseSupported()) { - purchaseHelper = getMyApplication().getInAppPurchaseHelper(); - } - } + private boolean activityDestroyed; @Override protected void onResume() { @@ -53,17 +48,36 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In protected void onDestroy() { super.onDestroy(); deinitInAppPurchaseHelper(); + activityDestroyed = true; } private void initInAppPurchaseHelper() { deinitInAppPurchaseHelper(); - - if (purchaseHelper != null) { - purchaseHelper.setUiActivity(this); - if (purchaseHelper.needRequestInventory()) { - purchaseHelper.requestInventory(); + if (purchaseHelper == null) { + InAppPurchaseHelper purchaseHelper = getMyApplication().getInAppPurchaseHelper(); + if (isInAppPurchaseAllowed() && isInAppPurchaseSupported(purchaseHelper)) { + this.purchaseHelper = purchaseHelper; } } + if (purchaseHelper != null) { + final WeakReference activityRef = new WeakReference<>(this); + purchaseHelper.isInAppPurchaseSupported(this, new InAppPurchaseInitCallback() { + @Override + public void onSuccess() { + OsmandInAppPurchaseActivity activity = activityRef.get(); + if (!activityDestroyed && AndroidUtils.isActivityNotDestroyed(activity)) { + purchaseHelper.setUiActivity(activity); + if (purchaseHelper.needRequestInventory()) { + purchaseHelper.requestInventory(); + } + } + } + + @Override + public void onFail() { + } + }); + } } private void deinitInAppPurchaseHelper() { @@ -80,7 +94,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper(); if (purchaseHelper != null) { app.logEvent("in_app_purchase_redirect"); - purchaseHelper.purchaseFullVersion(activity); + try { + purchaseHelper.purchaseFullVersion(activity); + } catch (UnsupportedOperationException e) { + LOG.error("purchaseFullVersion is not supported", e); + } } } else { app.logEvent("paid_version_redirect"); @@ -101,7 +119,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper(); if (purchaseHelper != null) { app.logEvent("depth_contours_purchase_redirect"); - purchaseHelper.purchaseDepthContours(activity); + try { + purchaseHelper.purchaseDepthContours(activity); + } catch (UnsupportedOperationException e) { + LOG.error("purchaseDepthContours is not supported", e); + } } } } @@ -129,8 +151,9 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In return false; } - public boolean isInAppPurchaseSupported() { - return Version.isGooglePlayEnabled(getMyApplication()); + public boolean isInAppPurchaseSupported(InAppPurchaseHelper purchaseHelper) { + OsmandApplication app = getMyApplication(); + return Version.isGooglePlayEnabled(app) || Version.isHuawei(app); } @Override @@ -222,6 +245,17 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In } } + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + boolean handled = false; + if (purchaseHelper != null) { + handled = purchaseHelper.onActivityResult(this, requestCode, resultCode, data); + } + if (!handled) { + super.onActivityResult(requestCode, resultCode, data); + } + } + public void onInAppPurchaseError(InAppPurchaseTaskType taskType, String error) { // not implemented } diff --git a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java index 855f648332..a4bb6ec74f 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java @@ -20,12 +20,14 @@ import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import net.osmand.AndroidNetworkUtils; +import net.osmand.PlatformUtil; import net.osmand.osm.AbstractPoiType; import net.osmand.osm.MapPoiTypes; import net.osmand.osm.PoiCategory; import net.osmand.osm.PoiType; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.activities.OsmandInAppPurchaseActivity; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.Version; @@ -56,7 +58,7 @@ import java.util.Map; public class DiscountHelper { private static final String TAG = "DiscountHelper"; - //private static final String DISCOUNT_JSON = "discount.json"; + private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(DiscountHelper.class); private static long mLastCheckTime; private static ControllerData mData; @@ -312,7 +314,11 @@ public class DiscountHelper { if (purchaseHelper != null) { if (url.contains(purchaseHelper.getFullVersion().getSku())) { app.logEvent("in_app_purchase_redirect"); - purchaseHelper.purchaseFullVersion(mapActivity); + try { + purchaseHelper.purchaseFullVersion(mapActivity); + } catch (UnsupportedOperationException e) { + LOG.error("purchaseFullVersion is not supported", e); + } } else { for (InAppPurchase p : purchaseHelper.getLiveUpdates().getAllSubscriptions()) { if (url.contains(p.getSku())) { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index c037c13d0a..ff734e6af5 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -2,6 +2,7 @@ package net.osmand.plus.inapp; import android.annotation.SuppressLint; import android.app.Activity; +import android.content.Intent; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; @@ -64,6 +65,7 @@ public abstract class InAppPurchaseHelper { protected InAppPurchaseListener uiActivity = null; public interface InAppPurchaseListener { + void onError(InAppPurchaseTaskType taskType, String error); void onGetItems(); @@ -75,6 +77,13 @@ public abstract class InAppPurchaseHelper { void dismissProgress(InAppPurchaseTaskType taskType); } + public interface InAppPurchaseInitCallback { + + void onSuccess(); + + void onFail(); + } + public enum InAppPurchaseTaskType { REQUEST_INVENTORY, PURCHASE_FULL_VERSION, @@ -82,9 +91,23 @@ public abstract class InAppPurchaseHelper { PURCHASE_DEPTH_CONTOURS } - public interface InAppRunnable { + public abstract class InAppCommand { + + InAppCommandResultHandler resultHandler; + // return true if done and false if async task started - boolean run(InAppPurchaseHelper helper); + abstract void run(InAppPurchaseHelper helper); + + protected void commandDone() { + InAppCommandResultHandler resultHandler = this.resultHandler; + if (resultHandler != null) { + resultHandler.onCommandDone(this); + } + } + } + + public interface InAppCommandResultHandler { + void onCommandDone(@NonNull InAppCommand command); } public static class PurchaseInfo { @@ -163,9 +186,10 @@ public abstract class InAppPurchaseHelper { public InAppPurchaseHelper(OsmandApplication ctx) { this.ctx = ctx; isDeveloperVersion = Version.isDeveloperVersion(ctx); - purchases = new InAppPurchases(ctx); } + public abstract void isInAppPurchaseSupported(@NonNull final Activity activity, @Nullable final InAppPurchaseInitCallback callback); + public boolean hasInventory() { return lastValidationCheckTime != 0; } @@ -181,7 +205,7 @@ public abstract class InAppPurchaseHelper { return false; } - protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { + protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppCommand command) { if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) { notifyDismissProgress(taskType); stop(true); @@ -205,14 +229,20 @@ public abstract class InAppPurchaseHelper { try { processingTask = true; activeTask = taskType; - execImpl(taskType, runnable); + command.resultHandler = new InAppCommandResultHandler() { + @Override + public void onCommandDone(@NonNull InAppCommand command) { + processingTask = false; + } + }; + execImpl(taskType, command); } catch (Exception e) { logError("exec Error", e); stop(true); } } - protected abstract void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable); + protected abstract void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppCommand command); public boolean needRequestInventory() { return !inventoryRequested && ((isSubscribedToLiveUpdates(ctx) && Algorithms.isEmpty(ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get())) @@ -224,16 +254,16 @@ public abstract class InAppPurchaseHelper { new RequestInventoryTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public abstract void purchaseFullVersion(final Activity activity) throws UnsupportedOperationException; + public abstract void purchaseFullVersion(@NonNull final Activity activity) throws UnsupportedOperationException; - public void purchaseLiveUpdates(Activity activity, String sku, String email, String userName, + public void purchaseLiveUpdates(@NonNull Activity activity, String sku, String email, String userName, String countryDownloadName, boolean hideUserName) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES); new LiveUpdatesPurchaseTask(activity, sku, email, userName, countryDownloadName, hideUserName) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public abstract void purchaseDepthContours(final Activity activity) throws UnsupportedOperationException; + public abstract void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException; @SuppressLint("StaticFieldLeak") private class LiveUpdatesPurchaseTask extends AsyncTask { @@ -329,8 +359,8 @@ public abstract class InAppPurchaseHelper { } } - protected abstract InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, - final String sku, final String payload) throws UnsupportedOperationException; + protected abstract InAppCommand getPurchaseLiveUpdatesCommand(final WeakReference activity, + final String sku, final String payload) throws UnsupportedOperationException; @SuppressLint("StaticFieldLeak") private class RequestInventoryTask extends AsyncTask { @@ -380,7 +410,7 @@ public abstract class InAppPurchaseHelper { } } - protected abstract InAppRunnable getRequestInventoryCommand() throws UnsupportedOperationException; + protected abstract InAppCommand getRequestInventoryCommand() throws UnsupportedOperationException; protected void onSkuDetailsResponseDone(List purchaseInfoList) { final AndroidNetworkUtils.OnRequestResultListener listener = new AndroidNetworkUtils.OnRequestResultListener() { @@ -599,6 +629,10 @@ public abstract class InAppPurchaseHelper { } } + public boolean onActivityResult(@NonNull Activity activity, int requestCode, int resultCode, Intent data) { + return false; + } + protected void notifyError(InAppPurchaseTaskType taskType, String message) { if (uiActivity != null) { uiActivity.onError(taskType, message); diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java index 8c42448ec7..5ccb49a70e 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java @@ -11,14 +11,11 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.billingclient.api.SkuDetails; - import net.osmand.AndroidUtils; import net.osmand.Period; import net.osmand.Period.PeriodUnit; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.Version; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.widgets.style.CustomTypefaceSpan; import net.osmand.util.Algorithms; @@ -33,72 +30,17 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class InAppPurchases { +public abstract class InAppPurchases { - private static final InAppPurchase FULL_VERSION = new InAppPurchaseFullVersion(); - private static final InAppPurchaseDepthContoursFull DEPTH_CONTOURS_FULL = new InAppPurchaseDepthContoursFull(); - private static final InAppPurchaseDepthContoursFree DEPTH_CONTOURS_FREE = new InAppPurchaseDepthContoursFree(); - private static final InAppPurchaseContourLinesFull CONTOUR_LINES_FULL = new InAppPurchaseContourLinesFull(); - private static final InAppPurchaseContourLinesFree CONTOUR_LINES_FREE = new InAppPurchaseContourLinesFree(); + protected InAppPurchase fullVersion; + protected InAppPurchase depthContours; + protected InAppPurchase contourLines; + protected InAppSubscription monthlyLiveUpdates; + protected InAppSubscription discountedMonthlyLiveUpdates; + protected InAppSubscriptionList liveUpdates; + protected InAppPurchase[] inAppPurchases; - private static final InAppSubscription[] LIVE_UPDATES_FULL = new InAppSubscription[]{ - new InAppPurchaseLiveUpdatesOldMonthlyFull(), - new InAppPurchaseLiveUpdatesMonthlyFull(), - new InAppPurchaseLiveUpdates3MonthsFull(), - new InAppPurchaseLiveUpdatesAnnualFull() - }; - - private static final InAppSubscription[] LIVE_UPDATES_FREE = new InAppSubscription[]{ - new InAppPurchaseLiveUpdatesOldMonthlyFree(), - new InAppPurchaseLiveUpdatesMonthlyFree(), - new InAppPurchaseLiveUpdates3MonthsFree(), - new InAppPurchaseLiveUpdatesAnnualFree() - }; - - private static final InAppSubscription[] LIVE_UPDATES_HW_FREE = new InAppSubscription[]{ - new InAppPurchaseLiveUpdatesMonthlyHWFree(), - new InAppPurchaseLiveUpdates3MonthsHWFree(), - new InAppPurchaseLiveUpdatesAnnualHWFree() - }; - - private InAppPurchase fullVersion; - private InAppPurchase depthContours; - private InAppPurchase contourLines; - private InAppSubscription monthlyLiveUpdates; - private InAppSubscription discountedMonthlyLiveUpdates; - private InAppSubscriptionList liveUpdates; - private InAppPurchase[] inAppPurchases; - - InAppPurchases(OsmandApplication ctx) { - fullVersion = FULL_VERSION; - if (Version.isHuawei(ctx)) { - liveUpdates = new LiveUpdatesInAppPurchasesHWFree(); - } else if (Version.isFreeVersion(ctx)) { - liveUpdates = new LiveUpdatesInAppPurchasesFree(); - } else { - liveUpdates = new LiveUpdatesInAppPurchasesFull(); - } - for (InAppSubscription s : liveUpdates.getAllSubscriptions()) { - if (s instanceof InAppPurchaseLiveUpdatesMonthly) { - if (s.isDiscounted()) { - discountedMonthlyLiveUpdates = s; - } else { - monthlyLiveUpdates = s; - } - } - } - if (Version.isFreeVersion(ctx)) { - depthContours = DEPTH_CONTOURS_FREE; - } else { - depthContours = DEPTH_CONTOURS_FULL; - } - if (Version.isFreeVersion(ctx)) { - contourLines = CONTOUR_LINES_FREE; - } else { - contourLines = CONTOUR_LINES_FULL; - } - - inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines }; + protected InAppPurchases(OsmandApplication ctx) { } public InAppPurchase getFullVersion() { @@ -166,31 +108,13 @@ public class InAppPurchases { return null; } - public boolean isFullVersion(String sku) { - return FULL_VERSION.getSku().equals(sku); - } + public abstract boolean isFullVersion(String sku); - public boolean isDepthContours(String sku) { - return DEPTH_CONTOURS_FULL.getSku().equals(sku) || DEPTH_CONTOURS_FREE.getSku().equals(sku); - } + public abstract boolean isDepthContours(String sku); - public boolean isContourLines(String sku) { - return CONTOUR_LINES_FULL.getSku().equals(sku) || CONTOUR_LINES_FREE.getSku().equals(sku); - } + public abstract boolean isContourLines(String sku); - public boolean isLiveUpdates(String sku) { - for (InAppPurchase p : LIVE_UPDATES_FULL) { - if (p.getSku().equals(sku)) { - return true; - } - } - for (InAppPurchase p : LIVE_UPDATES_FREE) { - if (p.getSku().equals(sku)) { - return true; - } - } - return false; - } + public abstract boolean isLiveUpdates(String sku); public abstract static class InAppSubscriptionList { @@ -268,27 +192,6 @@ public class InAppPurchases { } } - public static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList { - - public LiveUpdatesInAppPurchasesFree() { - super(LIVE_UPDATES_FREE); - } - } - - public static class LiveUpdatesInAppPurchasesHWFree extends InAppSubscriptionList { - - public LiveUpdatesInAppPurchasesHWFree() { - super(LIVE_UPDATES_HW_FREE); - } - } - - public static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList { - - public LiveUpdatesInAppPurchasesFull() { - super(LIVE_UPDATES_FULL); - } - } - public abstract static class InAppPurchase { public enum PurchaseState { @@ -310,11 +213,11 @@ public class InAppPurchases { private NumberFormat currencyFormatter; - private InAppPurchase(@NonNull String sku) { + protected InAppPurchase(@NonNull String sku) { this.sku = sku; } - private InAppPurchase(@NonNull String sku, boolean discounted) { + protected InAppPurchase(@NonNull String sku, boolean discounted) { this(sku); this.discounted = discounted; } @@ -792,23 +695,9 @@ public class InAppPurchases { } } - public static class InAppPurchaseFullVersion extends InAppPurchase { - - private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; - - InAppPurchaseFullVersion() { - super(SKU_FULL_VERSION_PRICE); - } - - @Override - public String getDefaultPrice(Context ctx) { - return ctx.getString(R.string.full_version_price); - } - } - public static class InAppPurchaseDepthContours extends InAppPurchase { - private InAppPurchaseDepthContours(String sku) { + protected InAppPurchaseDepthContours(String sku) { super(sku); } @@ -818,27 +707,9 @@ public class InAppPurchases { } } - public static class InAppPurchaseDepthContoursFull extends InAppPurchaseDepthContours { - - private static final String SKU_DEPTH_CONTOURS_FULL = "net.osmand.seadepth_plus"; - - InAppPurchaseDepthContoursFull() { - super(SKU_DEPTH_CONTOURS_FULL); - } - } - - public static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours { - - private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth"; - - InAppPurchaseDepthContoursFree() { - super(SKU_DEPTH_CONTOURS_FREE); - } - } - public static class InAppPurchaseContourLines extends InAppPurchase { - private InAppPurchaseContourLines(String sku) { + protected InAppPurchaseContourLines(String sku) { super(sku); } @@ -848,25 +719,7 @@ public class InAppPurchases { } } - public static class InAppPurchaseContourLinesFull extends InAppPurchaseContourLines { - - private static final String SKU_CONTOUR_LINES_FULL = "net.osmand.contourlines_plus"; - - InAppPurchaseContourLinesFull() { - super(SKU_CONTOUR_LINES_FULL); - } - } - - public static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines { - - private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines"; - - InAppPurchaseContourLinesFree() { - super(SKU_CONTOUR_LINES_FREE); - } - } - - public static abstract class InAppPurchaseLiveUpdatesMonthly extends InAppSubscription { + protected static abstract class InAppPurchaseLiveUpdatesMonthly extends InAppSubscription { InAppPurchaseLiveUpdatesMonthly(String skuNoVersion, int version) { super(skuNoVersion, version); @@ -920,64 +773,7 @@ public class InAppPurchases { } } - public static class InAppPurchaseLiveUpdatesMonthlyFull extends InAppPurchaseLiveUpdatesMonthly { - - private static final String SKU_LIVE_UPDATES_MONTHLY_FULL = "osm_live_subscription_monthly_full"; - - InAppPurchaseLiveUpdatesMonthlyFull() { - super(SKU_LIVE_UPDATES_MONTHLY_FULL, 1); - } - - private InAppPurchaseLiveUpdatesMonthlyFull(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFull(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly { - - private static final String SKU_LIVE_UPDATES_MONTHLY_FREE = "osm_live_subscription_monthly_free"; - - InAppPurchaseLiveUpdatesMonthlyFree() { - super(SKU_LIVE_UPDATES_MONTHLY_FREE, 1); - } - - private InAppPurchaseLiveUpdatesMonthlyFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyFree(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdatesMonthlyHWFree extends InAppPurchaseLiveUpdatesMonthly { - - private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.test.monthly"; - - InAppPurchaseLiveUpdatesMonthlyHWFree() { - super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1); - } - - private InAppPurchaseLiveUpdatesMonthlyHWFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyHWFree(sku) : null; - } - } - - public static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription { + protected static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription { InAppPurchaseLiveUpdates3Months(String skuNoVersion, int version) { super(skuNoVersion, version); @@ -1020,64 +816,7 @@ public class InAppPurchases { } } - public static class InAppPurchaseLiveUpdates3MonthsFull extends InAppPurchaseLiveUpdates3Months { - - private static final String SKU_LIVE_UPDATES_3_MONTHS_FULL = "osm_live_subscription_3_months_full"; - - InAppPurchaseLiveUpdates3MonthsFull() { - super(SKU_LIVE_UPDATES_3_MONTHS_FULL, 1); - } - - private InAppPurchaseLiveUpdates3MonthsFull(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFull(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months { - - private static final String SKU_LIVE_UPDATES_3_MONTHS_FREE = "osm_live_subscription_3_months_free"; - - InAppPurchaseLiveUpdates3MonthsFree() { - super(SKU_LIVE_UPDATES_3_MONTHS_FREE, 1); - } - - private InAppPurchaseLiveUpdates3MonthsFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsFree(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdates3MonthsHWFree extends InAppPurchaseLiveUpdates3Months { - - private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.test.3months"; - - InAppPurchaseLiveUpdates3MonthsHWFree() { - super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1); - } - - private InAppPurchaseLiveUpdates3MonthsHWFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsHWFree(sku) : null; - } - } - - public static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription { + protected static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription { InAppPurchaseLiveUpdatesAnnual(String skuNoVersion, int version) { super(skuNoVersion, version); @@ -1120,63 +859,6 @@ public class InAppPurchases { } } - public static class InAppPurchaseLiveUpdatesAnnualFull extends InAppPurchaseLiveUpdatesAnnual { - - private static final String SKU_LIVE_UPDATES_ANNUAL_FULL = "osm_live_subscription_annual_full"; - - InAppPurchaseLiveUpdatesAnnualFull() { - super(SKU_LIVE_UPDATES_ANNUAL_FULL, 1); - } - - private InAppPurchaseLiveUpdatesAnnualFull(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFull(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual { - - private static final String SKU_LIVE_UPDATES_ANNUAL_FREE = "osm_live_subscription_annual_free"; - - InAppPurchaseLiveUpdatesAnnualFree() { - super(SKU_LIVE_UPDATES_ANNUAL_FREE, 1); - } - - private InAppPurchaseLiveUpdatesAnnualFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualFree(sku) : null; - } - } - - public static class InAppPurchaseLiveUpdatesAnnualHWFree extends InAppPurchaseLiveUpdatesAnnual { - - private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.test.annual"; - - InAppPurchaseLiveUpdatesAnnualHWFree() { - super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1); - } - - private InAppPurchaseLiveUpdatesAnnualHWFree(@NonNull String sku) { - super(sku); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualHWFree(sku) : null; - } - } - public static class InAppPurchaseLiveUpdatesOldMonthly extends InAppPurchaseLiveUpdatesMonthly { InAppPurchaseLiveUpdatesOldMonthly(String sku) { @@ -1199,54 +881,5 @@ public class InAppPurchases { return null; } } - - public static class InAppPurchaseLiveUpdatesOldMonthlyFull extends InAppPurchaseLiveUpdatesOldMonthly { - - private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FULL = "osm_live_subscription_2"; - - InAppPurchaseLiveUpdatesOldMonthlyFull() { - super(SKU_LIVE_UPDATES_OLD_MONTHLY_FULL); - } - } - - public static class InAppPurchaseLiveUpdatesOldMonthlyFree extends InAppPurchaseLiveUpdatesOldMonthly { - - private static final String SKU_LIVE_UPDATES_OLD_MONTHLY_FREE = "osm_free_live_subscription_2"; - - InAppPurchaseLiveUpdatesOldMonthlyFree() { - super(SKU_LIVE_UPDATES_OLD_MONTHLY_FREE); - } - } - - public static class InAppPurchaseLiveUpdatesOldSubscription extends InAppSubscription { - - private SkuDetails details; - - InAppPurchaseLiveUpdatesOldSubscription(@NonNull SkuDetails details) { - super(details.getSku(), true); - this.details = details; - } - - @Override - public String getDefaultPrice(Context ctx) { - return ""; - } - - @Override - public CharSequence getTitle(Context ctx) { - return details.getTitle(); - } - - @Override - public CharSequence getDescription(@NonNull Context ctx) { - return details.getDescription(); - } - - @Nullable - @Override - protected InAppSubscription newInstance(@NonNull String sku) { - return null; - } - } } From 9af2ccea51643daceee9fa443c36d655b75b7bc4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Oct 2020 15:38:57 +0300 Subject: [PATCH 17/75] Update direction arrow icons for track direction --- OsmAnd/res/drawable/ic_action_direction_arrow.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/drawable/ic_action_direction_arrow.xml b/OsmAnd/res/drawable/ic_action_direction_arrow.xml index 5e97fe8338..b13a8692f7 100644 --- a/OsmAnd/res/drawable/ic_action_direction_arrow.xml +++ b/OsmAnd/res/drawable/ic_action_direction_arrow.xml @@ -1,9 +1,9 @@ + android:viewportHeight="9"> From 8bec1264114ea15a6ee66629bcd1cd3ef7113d3d Mon Sep 17 00:00:00 2001 From: Evgenii Martynenko Date: Fri, 2 Oct 2020 16:00:53 +0000 Subject: [PATCH 18/75] Translated using Weblate (Russian) Currently translated at 100.0% (3487 of 3487 strings) --- OsmAnd/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 701641a64d..7e4833744e 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -171,7 +171,7 @@ Добавить источник карты Источник карты изменён на «%s». Удерживайте кнопку для перемещения её по экрану. - Показывать контуры и точки глубины. + Показывать контуры и точки глубин. Контуры морских глубин Частота горизонталей Частота горизонталей @@ -1739,7 +1739,7 @@ Отменить выбор всех Поделиться Мои места - Точки + Избранные Треки Текущий трек Поделиться заметкой From 8eaecb105a5f652d07f5026b94f45cbcc671537e Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Fri, 2 Oct 2020 14:51:30 +0000 Subject: [PATCH 19/75] Translated using Weblate (Russian) Currently translated at 100.0% (3487 of 3487 strings) --- OsmAnd/res/values-ru/strings.xml | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 7e4833744e..8dcc9c13c0 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -115,7 +115,7 @@ Сбалансированный Предпочитать переулки Выберите предпочтительный рельеф. - Склон + Карта уклонов Добавить новую папку Точки удалены. Вы уверены, что хотите удалить %1$d точки\? @@ -368,9 +368,9 @@ Использовать онлайн-карты (загрузка и кеширование на SD-карте). Онлайн-карты Выберите источник онлайн или кешированных растровых карт. - Доступ ко множеству онлайн-карт (т. н. тайловых или растровых): от встроенных OSM (как Mapnik), до спутниковых снимков и слоёв специального назначения, таких как карты погоды, климатические, геологические карты, затенения рельефа и др. + Доступ ко множеству онлайн-карт (т. н. тайловых или растровых): от встроенных OSM (как Mapnik) до спутниковых снимков и слоёв специального назначения, таких как карты погоды, климатические, геологические карты, затенения рельефа и др. \n -\n Любая из этих карт может быть использована в качестве базовой либо как наложение или подложка к другой базовой карте (например стандартной локальной карте OsmAnd). Некоторые элементы векторной карты OsmAnd можно скрыть в меню «Настройки карты». +\n Любая из этих карт может быть использована как основная или в качестве подложки к другой карте (например стандартной локальной карте OsmAnd). Некоторые элементы векторной карты OsmAnd можно скрыть в меню «Настройки карты». \n \n Карты можно загрузить непосредственно из интернета или подготовить для использования в автономном режиме (и вручную скопировать в папку данных OsmAnd) в виде базы данных sqlite, которая может быть создана с помощью различных инструментов подготовки карт сторонних производителей. Показывает настройки для включения фонового отслеживания и навигации путём периодического пробуждения устройства GPS (с выключенным экраном). @@ -518,9 +518,9 @@ Обратное направление трека Использовать текущий пункт назначения Пройти весь путь - Для этого региона доступны локальные векторные карты. -\n\t -\n\tДля их использования выберите в \"Меню\" → \"Настройка карты\" → \"Источник карты…\" → \"Векторные карты\". + Для этого региона есть локальные векторные карты. +\n\t +\n\tДля использования выберите их в качестве источника (Меню → Настройка карты → Источник карты → Локальные векторные карты). Голосовые инструкции Выберите канал вывода голосовых подсказок. Канал голосовых звонков (прерывает автомобильную Bluetooth стереосистему) @@ -1205,8 +1205,8 @@ Сделать фото Синхронизация треков и медиазаметок с вашим аккаунтом Dropbox. Плагин Dropbox - Плагин обеспечивает наложение контурных линии и (рельефа) затемняющего слоя, которые будут отображаться поверх стандартных карт OsmAnd. Эта функция высоко оценится спортсменами, туристами, путешественниками и всеми, кто заинтересован в рельефной структуре ландшафта. -\n + Плагин обеспечивает наложение контурных линии и затемняющего слоя (рельефа), которые будут отображаться поверх стандартных карт OsmAnd. Эту функцию оценят спортсмены, туристы, путешественники и все, для кого рельеф местности имеет значение. +\n \nГлобальные данные (между 70° на севере и 70° на юге) основываются на измерениях SRTM (Shuttle Radar Topography Mission) и ASTER (Advanced Spaceborne Thermal Emission and Reflection Radiometer), инструментом визуализации Terra, флагманского спутника Земли системы наблюдения NASA. ASTER является результатом совместных усилий NASA, министерства экономики Японии, торговли и промышленности (METI), космических систем Японии (J-spacesystems). Фото %1$s %2$s Медиаданные @@ -1227,7 +1227,7 @@ Время прибытия Информация GPS нет - Слой рельефа местности + Слой затенения рельефа Название улицы Номер дома Нет соединения по Wi-Fi. Использовать текущее интернет-соединение для загрузки\? @@ -1780,7 +1780,7 @@ \n \nВ случае активации этого вида, стиль карты меняется на «Зимний/лыжный», показывая все детали пейзажа так, как они выглядят зимой. Такой (зимний) вид может быть отменён либо путём деактивации здесь, либо если вы поменяете «Стиль карты» в меню «Настройки карты» на желаемый вид. Текущий маршрут - Скачать карты + Загрузка карт Для правильного отображения дорожных знаков и правил выберите свой регион вождения: Добро пожаловать Отметить для удаления @@ -1905,8 +1905,8 @@ Пропустить OsmAnd Плагины - Локальные карты -\nи Навигация + Офлайн-карты +\nи навигация Номер дома Тип лыжной трассы Автообновления @@ -2764,7 +2764,7 @@ Начать редактирование Получить неограниченный доступ Добро пожаловать на открытое бета-тестирование - Карты горизонталей и карты с отмывкой рельефа + Карты горизонталей и затенение рельефа Скачать статьи Википедии для %1$s, чтобы читать их в автономном режиме. Загрузка данных Википедии Открыть статью в интернете @@ -3366,7 +3366,7 @@ Маршруты, подготовленные для фристайла или катания только на коньках без классических треков. Разрешить только классические маршруты Маршруты, подготовленные только для классического стиля без конькобежных трасс. Сюда входят маршруты, подготовленные небольшим снегоходом с более свободной лыжнёй и трассами, подготовленные вручную лыжниками. - Предпочитать маршруты заданной сложности, хотя прокладка маршрута по более сложным или лёгким трассам всё же возможна, если они короче. + Предпочтительный уровень сложности маршрутов. Возможно использование более сложных или лёгких путей, если они короче. Включать на повороте Класс 1 Класс 2 @@ -3553,7 +3553,7 @@ Отклонение, при котором маршрут будет пересчитан. Легенда Невозможно разобрать геоссылку «%s». - Для отображения затенения рельефа на карте необходимы дополнительные карты. + Для отображения затенения рельефа требуются дополнительные карты. Мин. Отображение затенения рельефа или карты уклонов. Подробнее об этих типах карт вы можете прочитать на нашем сайте. Прозрачность @@ -3582,11 +3582,11 @@ Укажите веб-адрес со следующими параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}. В этом случае будут записываться только точки, измеренные с минимальной точностью (в метрах/футах согласно настройкам устройства). Точность — это близость измерений к истинному местоположению и не имеет прямого отношения к точности, подразумевающейся под разбросом повторных замеров. Рекомендация: попробуйте сначала воспользоваться детектором движения через фильтр минимального смещения (B), что может дать лучшие результаты и вы потеряете меньше данных. Если треки остаются шумными на низких скоростях, попробуйте использовать ненулевые значения. Обратите внимание, что некоторые измерения могут вообще не указывать значения скорости (некоторые сетевые методы), и в этом случае ничего не будет записываться. - Уклон использует цвета для визуализации крутизны рельефа. + Для визуализации крутизны рельефа используются цвета. Подробнее об уклонах можно прочитать в %1$s. Затенение рельефа - Затенение рельефа использует тёмные оттенки для отображения склонов, вершин и низменностей. - Для отображения склонов на карте необходимы дополнительные карты. + Для отображения склонов, вершин и низменностей используются тёмные тени. + Для отображения склонов требуются дополнительные карты. Уклоны Заменить этой точкой другую. Изменения применены к профилю «%1$s». @@ -3883,17 +3883,17 @@ сохранен Добавьте хотя бы две точки. ПОВТОРИТЬ - • Обновлённый режим планирования маршрута позволяет использовать разные типы навигации для каждого сегмента и прикрепляет любой трек к дорогам + • Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам \n -\n • Новые параметры внешнего вида для треков: можно выбрать цвет, толщину, включите стрелки направления и отметки начала/окончания +\n • Новые настройки вида треков: выбор цвета и толщины линии, стрелки направления, метки начала и конца маршрута \n -\n • Улучшена видимость велосипедных узлов +\n • Повышенная видимость велосипедных узлов \n -\n • Контекстное меню для треков с основной информацией +\n • Контекстное меню с основной информацией для треков \n \n • Улучшенные алгоритмы поиска \n -\n • Улучшены параметры следования по треку в навигации +\n • Улучшенные настройки следования по треку в Навигации \n \n • Исправлены проблемы с импортом/экспортом настроек профиля \n From a528b16846c97dadc96e18f2f4824a88452b8a40 Mon Sep 17 00:00:00 2001 From: ssantos Date: Thu, 1 Oct 2020 21:02:14 +0000 Subject: [PATCH 20/75] Translated using Weblate (Portuguese) Currently translated at 100.0% (3487 of 3487 strings) --- OsmAnd/res/values-pt/strings.xml | 129 ++++++++++++++++--------------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index fe6c5d23b1..d700a97de6 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -239,7 +239,7 @@ Meio de transporte: Por favor, define o destino primeiro Navegação - A aplicação do estado do GPS não está instalada. Pesquisar na loja de aplicações\? + A app do estado do GPS não está instalada. Pesquisar na loja de apps\? Horas de abertura Abrindo conjunto de alterações … Fechando conjunto de alterações… @@ -294,7 +294,7 @@ Adicionar aos \'Favoritos\' Escolher entre os nomes nativos e inglês. Usar nomes em inglês - Configurações da aplicação + Configurações da app Pesquisar endereço Escolher edifício Escolher rua @@ -486,7 +486,7 @@ Usar cores fluorescentes para mostrar trajetos e rotas. Edição offline Usar sempre a edição offline. - As alterações de POI dentro da aplicação não afetam os ficheiros de mapas descarregados; essas alterações são guardadas num ficheiro separado no seu aparelho. + As alterações de POI dentro da app não afetam os ficheiros de mapas descarregados; essas alterações são guardadas num ficheiro separado no seu aparelho. A enviar… {0} POI/anotações enviados Enviar todos @@ -523,7 +523,7 @@ Já existe um ficheiro de favoritos exportados anteriormente. Quer substitui-lo\? Configurações específicas de Perfil Configurações Globais - Configurações globais da aplicação + Configurações globais da app Espaço livre insuficiente, precisa de %1$s MB (só tem: %2$s disponíveis). Descarregar {0} ficheiro(s)\? \n {1} MB (de {2} MB) será utilizado. @@ -554,7 +554,7 @@ O ficheiro POI \'%1$s\' é redundante e pode ser eliminado. Não foi encontrado (e não pôde ser criado) o ficheiro local para guardar as mudanças de POI. Upgrade para OsmAnd+ - Descarregue a nova versão da aplicação para poder usar os novos ficheiros de mapas. + Descarregue a nova versão da app para poder usar os novos ficheiros de mapas. Mudar o nome Online Nomeação Procurando posição… @@ -624,7 +624,7 @@ Áudio de chamada telefónica (para interromper os aparelhos de som Bluetooth do carro) Áudio de Notificação Áudio de mídia/navegação - A aplicação não conseguiu descarregar a camada do mapa %1$s, se a tornar a instalar pode resolver o problema. + A app não conseguiu descarregar a camada do mapa %1$s, se a tornar a instalar pode resolver o problema. Ajustar a transparência da sobreposição. Transparência da Sobreposição Ajustar a transparência do mapa base. @@ -646,7 +646,7 @@ Não foi possível executar a pesquisa offline. Pesquisa por localização geográfica Sistema - Idioma de exibição da aplicação (usado após OsmAnd ser reiniciado). + Idioma de exibição da app (usado após OsmAnd ser reiniciado). Linguagem Próximo Anterior @@ -671,7 +671,7 @@ \nO serviço de navegação está temporariamente mudado para CloudMade on-line. Não foi possível encontrar a pasta especificada. Local de armazenamento - Todos os dados offline na aplicação instalada antiga serão suportados pela nova aplicação, mas os pontos Favoritos devem ser exportados da aplicação antiga e depois importados na nova aplicação. + Todos os dados offline na app instalada antiga serão suportados pela nova, mas os pontos Favoritos devem ser exportados da app antiga e depois importados na nova. Build {0} foi instalado ({1}). Descarregando construção… Instalar OsmAnd - {0} de {1} {2} MB \? @@ -787,27 +787,27 @@ Widgets transparentes Contínuo e-mail - OsmAnd (direções automatizadas de navegação OSM) -\n -\nO OsmAnd é uma aplicação de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz. -\n -\nAlgumas das características principais: -\n- Funcionalidade totalmente desligado da Internet (guarda os mapas obtidos, sejam eles vetoriais ou imagens, numa pasta selecionável). -\n- Mapas vetoriais compactados do mundo inteiro disponíveis. -\n- Descarregar mapas de países ou regiões diretamente na aplicação. -\n- Sobreposição de mapas diversos, como GPX ou trajetos de navegação, pontos de interesse (POI), favoritos, curvas de nível, paragens de transportes públicos, mapas adicionais com transparência personalizável. -\n- Pesquisa desligado da Internet para endereços e locais (POIs). -\n- Encaminhamento desligado da Internet para distâncias médias. -\n- Modo de carro, bicicleta e pedestre. -\n- Vista de dia/noite, com alteração automática (opcional). -\n- Ampliação do mapa dependente da velocidade. -\n- Orientação do mapa de acordo com bússola ou direção do movimento. -\n- Orientação de faixas de rodagem, aviso de limite de velocidade, vozes gravadas e vozes para a conversão de texto para voz. -\n -\nLimitações desta versão gratuita do OsmAnd: -\n- Quantidade de descarregamentos de mapas limitado. -\n- Sem acesso aos POIs da Wikipédia no modo desligado da Internet. -\n + OsmAnd (direções automatizadas de navegação OSM) +\n +\nO OsmAnd é uma app de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz. +\n +\nAlgumas das características principais: +\n- Funcionalidade totalmente desligado da Internet (guarda os mapas obtidos, sejam eles vetoriais ou imagens, numa pasta selecionável). +\n- Mapas vetoriais compactados do mundo inteiro disponíveis. +\n- Descarregar mapas de países ou regiões diretamente na app. +\n- Sobreposição de mapas diversos, como GPX ou trajetos de navegação, pontos de interesse (POI), favoritos, curvas de nível, paragens de transportes públicos, mapas adicionais com transparência personalizável. +\n- Pesquisa desligado da Internet para endereços e locais (POIs). +\n- Encaminhamento desligado da Internet para distâncias médias. +\n- Modo de carro, bicicleta e pedestre. +\n- Vista de dia/noite, com alteração automática (opcional). +\n- Ampliação do mapa dependente da velocidade. +\n- Orientação do mapa de acordo com bússola ou direção do movimento. +\n- Orientação de faixas de rodagem, aviso de limite de velocidade, vozes gravadas e vozes para a conversão de texto para voz. +\n +\nLimitações desta versão gratuita do OsmAnd: +\n- Quantidade de descarregamentos de mapas limitado. +\n- Sem acesso aos POIs da Wikipédia no modo desligado da Internet. +\n \nO OsmAnd está em desenvolvimento ativo, mas o nosso projeto e o seu progresso ainda depende de contribuições financeiras para o desenvolvimento e testes de novas funcionalidades. Por favor, considere comprar o OsmAnd+, ou a ajudar a financiar novas funcionalidades específicas, ou fazer um donativo no osmand.net. Selecione um esquema de cores de estrada: Esquema de cores @@ -846,9 +846,9 @@ Só Estradas Mapa padrão Mapas só de estradas - Executar a aplicação no modo de segurança (usando o código do Android mais lento em vez do nativo). + Executar a app no modo de segurança (usando o código do Android mais lento em vez do nativo). Modo seguro - A aplicação está a ser executada no modo de segurança (desligue-a em \"Definições\"). + A app está a ser executada no modo de segurança (desligue-a em \"Definições\"). O serviço de segundo plano OsmAnd ainda está em execução. Tambẽm pará-lo\? Fechar conjunto de alterações Pesquisar mais povoações/códigos postais @@ -893,7 +893,7 @@ Sob demanda\? Formato de saída de vídeo: Usar gravador do sistema para vídeo. - Utilize aplicação do sistema para fotos. + Utilize a app do sistema para fotos. Usar aplicação da câmara A tocar o áudio da gravação. \n%1$s Indisponível @@ -927,14 +927,14 @@ desmarcado Limite de Velocidade Nenhum edifício encontrado. - A aplicação do leitor de código de barras ZXing não está instalada. Procurar no Google Play\? + A app do leitor de código de barras ZXing não está instalada. Procurar no Google Play\? Faça uma doação para ver novas funcionalidades implementadas nesta aplicação. incompleto Nome da rua Número de casa Gravação de viagem - Personalizar a aparência da aplicação. - Tema da aplicação + Personalizar a aparência da app. + Tema da app Opções de acessibilidade Especifique um endereço Selecione favorito @@ -1047,14 +1047,14 @@ Mapa mundial OsmAnd+ (Direções de Navegação Automatizada do OSM) \n -\n OsmAnd+ é uma aplicação de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz. +\n OsmAnd+ é uma app de navegação livre, com acesso a uma ampla variedade de dados globais do OSM. Todos os dados dos mapas (mapas vetoriais ou imagens raster) podem ser armazenados no cartão de memória do telemóvel para usar desligado da Internet. O OsmAnd também permite roteamento, tanto ligado como desligado da Internet, incluindo a funcionalidade de roteamento curva a curva com orientação por voz. \n -\n OsmAnd+ é a versão paga da aplicação, ao comprá-lo está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações. +\n OsmAnd+ é a versão paga da app, ao comprá-lo está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações. \n \n Algumas das características principais: \n - Funcionalidade totalmente desligado da Internet (guarda os mapas obtidos, sejam eles vetoriais ou imagens, numa pasta selecionável). \n - Mapas vetoriais compactados do mundo inteiro disponíveis. -\n - Descarregamento de mapas de países ou regiões diretamente na aplicação. +\n - Descarregamento de mapas de países ou regiões diretamente na app. \n - Recurso Wikipédia desligado da Internet (descarregamento de POIs da Wikipédia), ótimo para passeios turísticos. \n - Possibilidade de sobreposição de várias camadas de mapas, como trilhos GPX ou navegação, pontos de Interesse, favoritos, curvas de nível, paragens de transporte público, mapas adicionais com transparência personalizável. \n @@ -1086,7 +1086,7 @@ Wikipédia (off-line) Marca Marítima Escolha perfis visíveis. - Perfis da aplicação + Perfis da app Destino Rosa Castanho @@ -1341,7 +1341,7 @@ Ver Norte Leste - Memória interna da aplicação + Memória interna da app Ir Configurações de navegação Configurações gerais @@ -1521,8 +1521,8 @@ O mapa %1$s está pronto para ser usado. Mapa descarregado Mostrar mapa - Define o sinalizador que indica a primeira inicialização da aplicação, mantém todas as outras configurações inalteradas. - Simular arranque inicial da aplicação + Define o sinalizador que indica a primeira inicialização da app, mantém todas as outras configurações inalteradas. + Simular arranque inicial da app geo: Partilhar Localização Enviar @@ -1572,7 +1572,7 @@ Escolher orientação por voz Escolher ou descarregar a orientação por voz para o seu idioma. Noite - Há uma nova opção para controlar principalmente a aplicação através do painel de controlo flexível ou um menu estático. A sua escolha pode ser alterada nas configurações do painel. + Há uma nova opção para controlar principalmente a app através do painel de controlo flexível ou um menu estático. A sua escolha pode ser alterada nas configurações do painel. Painel de controlo ou menu de controlo Atualizar Apenas descarregar com Wi-Fi @@ -1661,7 +1661,7 @@ Fino Média Negrito - Agora a aplicação está autorizada a escrever no armazenamento externo, mas primeiro é necessário reiniciar a aplicação. + Agora a app está autorizada a escrever no armazenamento externo, mas primeiro é necessário reiniciar a app. Mover ↑ Mover ↓ Terminar a navegação @@ -1959,7 +1959,7 @@ Sem sobreposição Sem subposição Erro - Assine a nossa lista de e-mail sobre descontos da aplicação e ganhe mais 3 descarregamentos de mapas! + Assine a nossa lista de e-mail sobre descontos da app e ganhe mais 3 descarregamentos de mapas! Curvas de nível de profundidade marítima e seamarks. Muito obrigado por comprar \'Contornos de profundidade náutica\' Contornos de profundidade náutica @@ -1973,10 +1973,10 @@ Fontes do mapa Circulação pela direita Automático - Não envie estatísticas anónimas de utilização da aplicação - OsmAnd recolhe informação sobre as secções da aplicação que abriu. Não são enviadas: a sua localização; a informação que introduz na aplicação; detalhes de áreas que veja, procure ou descarregue. + Não envie estatísticas anónimas de utilização da app + OsmAnd recolhe informação sobre as secções da app que abriu. Não são enviadas: a sua localização; a informação que introduz na app; detalhes de áreas que veja, procure ou descarregue. Não mostrar mensagens ao iniciar - Nâo mostrar descontos da aplicação e mensagens de eventos locais especiais. + Nâo mostrar descontos da app e mensagens de eventos locais especiais. Opções de estacionamento Muito obrigado por comprar a versão paga de OsmAnd. Inclinado @@ -2208,7 +2208,7 @@ Como abrir a hiperligação\? Ler a Wikipédia desligado da Internet Descarregar tudo - Reiniciar a aplicação + Reiniciar a app Mostrar imagens Cancelou a sua assinatura do OsmAnd Live Renovar assinatura para continuar a utilizar todas as funcionalidades: @@ -2232,7 +2232,7 @@ Guias para os lugares mais interessantes do mundo dentro do OsmAnd, sem uma conexão com a Internet. Atualizações de mapa mensais Atualizações de mapa a cada hora - Compra na aplicação + Compra na app Pagamento de uma só vez Uma vez comprado, estará sempre disponível para si. Comprar - %1$s @@ -2475,7 +2475,7 @@ \nRepresenta área: %1$s x %2$s Tolerância do limite de velocidade Selecione a margem de tolerância de limite de velocidade, acima do qual receberá um aviso de voz. - O nome do Favorito foi modificado para %1$s para facilitar gravar corretamente a sequência de caracteres com emoticons para um ficheiro. + O nome do favorito foi modificado para %1$s para facilitar gravar corretamente a cadeia de caracteres com emoticons num ficheiro. Imprimir rota Nome de favorito duplicado Nome favorito especificado já está em uso, foi alterado para %1$s para evitar a duplicação. @@ -2551,8 +2551,8 @@ Renderizar caminhos de acordo com a escala de SAC. Renderizar caminhos de acordo com traços OSMC. Hora intermediária - OsmAnd (sigla em inglês de direções de navegação automatizada do OSM) é uma aplicação de mapas e navegação com acesso a dados livres, mundiais e de alta qualidade do OSM. -\n + OsmAnd (sigla em inglês de direções de navegação automatizada do OSM) é uma app de mapas e navegação com acesso a dados livres, mundiais e de alta qualidade do OSM. +\n \nPoderá usar o navegador visual e por voz, ver POIs (pontos de interesse), criar e gerir trilhos GPX, usar (através de um suplemento) curvas de nível e dados de altitude, escolher entre os modos motorista, ciclista e pedestre, editar o OpenStreetMap e muito mais. Navegação GPS \n• Escolha entre modos off-line (sem tarifa de roaming quando estiver no exterior) ou on-line (mais rápido) @@ -2595,7 +2595,7 @@ \n • Envie trilhos GPX para o OpenStretMap diretamente da aplicação \n • Adicione POIs e envie-os diretamente para o OpenStretMap (ou mais tarde se estiver desconectado da Internet) \n - OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a aplicação reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades. + OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a app reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades. \n Cobertura de mapa e qualidade aproximada: \n • Europa Ocidental: **** \n • Europa Oriental: *** @@ -2609,11 +2609,11 @@ \n • Antártida: * \n A maioria dos países ao redor do globo está disponível para descarregar! \n Obtenha um navegador confiável no seu país - seja em França, Alemanha, México, Reino Unido, Espanha, Holanda, EUA, Rússia, Brasil ou qualquer outro. - OsmAnd+ (direções de navegação automatizada do OSM) é uma aplicação de mapas e navegação com acesso a dados livres do OSM, de todo o mundo e de alta qualidade. -\nDesfrute da navegação visual ou por voz, vendo POIs (pontos de interesse), criando e gerindo trilhos GPX, usando informação de altitude e curvas de nível, escolher entre modos dirigir, andar de bicicleta e pedestre, editar o OpenStreetMap e muito mais. -\n -\nOsmAnd+ é a versão paga da aplicação. Ao comprá-lo, está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações. -\n + OsmAnd+ (direções de navegação automatizada do OSM) é uma app de mapas e navegação com acesso a dados livres do OSM, de todo o mundo e de alta qualidade. +\nDesfrute da navegação visual ou por voz, ver POIs (pontos de interesse), criando e gerindo trilhos GPX, usando informação de altitude e curvas de nível, escolher entre modos dirigir, andar de bicicleta e pedestre, editar o OpenStreetMap e muito mais. +\n +\nOsmAnd+ é a versão paga da app. Ao comprá-lo, está a apoiar o projeto, a financiar o desenvolvimento de novas funcionalidades e a receber as últimas atualizações. +\n \nAlgumas das características principais: Navegação \n• Funciona on-line (rápido) ou off-line (sem custos de roaming quando estiver no estrangeiro) @@ -2654,10 +2654,10 @@ \n• Visualização de curvas de nível e sombreamento de relevo (via suplemento adicional) Contribua diretamente para o OpenStreetMap \n • Envie relatórios de erros. -\n • Envie trilhos GPX para o OpenStretMap diretamente da aplicação. +\n • Envie trilhos GPX para o OpenStretMap diretamente da app. \n • Adicione POIs e envie-os diretamente para o OpenStretMap (ou mais tarde se estiver desconectado da Internet). \n • Gravação de viagem opcional também em plano de fundo (enquanto o aparelho está no modo adormecido). -\n OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a aplicação reportando erros, melhorando as traduções ou programando novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades. +\n OsmAnd é um programa de fonte aberta desenvolvido ativamente. Todos podem contribuir para a app por reportar erros, a melhorar as traduções ou a programar novas funcionalidades. Além disso, o projeto conta com contribuições financeiras para financiar a programação e testes de novas funcionalidades. \n Cobertura de mapa e qualidade aproximada: \n• Europa Ocidental: **** @@ -3358,7 +3358,7 @@ Tocar em \'Aplicar\' apagará os perfis removidos permanentemente. Perfil principal Selecione a cor - Perfis padrão do OsmAnd não podem ser apagados, mas desativados (na tela anterior) ou classificados na parte inferior. + Perfis predefinidos do OsmAnd não podem ser apagados, mas desativados (no ecrã anterior) ou classificados na parte inferior. Editar perfis O \'Tipo de navegação\' controla como as rotas são calculadas. Aspeto do perfil @@ -3887,7 +3887,7 @@ Apenas a linha da rota será gravada, os pontos de passagem serão apagados. Nome do ficheiro %s ficheiros de faixa selecionados - Vai pausar o registo de faixas quando a aplicação for morta (através de aplicações recentes). (indicação de fundo de OsmAnd desaparece da barra de notificação do Android.) + Vai pausar o registo de faixas quando a app for morta (através de apps recentes). (indicação de fundo de OsmAnd desaparece da barra de notificação do Android.) - Função atualizada de Planear uma rota: permite utilizar diferentes tipos de navegação por segmento e a inclusão de faixas \n \n - Novo menu Aparência para trilhos: selecionar cor, espessura, setas de direção de visualização, ícones de início/fim @@ -3903,4 +3903,7 @@ \n - Problemas com as configurações de importação/exportação de perfis resolvidos \n \n + Última modificação + Nome: Z – A + Nome: A – Z \ No newline at end of file From cd73208f21586010d9650d83d9995400f1592f1b Mon Sep 17 00:00:00 2001 From: Hinagiku Zeppeki Date: Fri, 2 Oct 2020 09:15:16 +0000 Subject: [PATCH 21/75] Translated using Weblate (Japanese) Currently translated at 98.7% (3443 of 3487 strings) --- OsmAnd/res/values-ja/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/OsmAnd/res/values-ja/strings.xml b/OsmAnd/res/values-ja/strings.xml index 80f0b62286..565e5ae9d6 100644 --- a/OsmAnd/res/values-ja/strings.xml +++ b/OsmAnd/res/values-ja/strings.xml @@ -1190,7 +1190,7 @@ POIの更新は利用できません "出発時刻: %1$tF, %1$tT " "到着時刻: %1$tF, %1$tT " "平均速度: %1$s " - "最高速度: %1$s " + 最高速度: %1$s 平均標高: %1$s 標高差: %1$s 上り/下り: %1$s @@ -1350,7 +1350,7 @@ POIの更新は利用できません 画面の電源オン設定 方向転換地点に近づいたらデバイスの画面を(オフの場合指定時間)オンにします しない - 除外する道の指定… + 避ける道の指定… 電車でのルート 路面電車でのルート タクシーのルート共有 @@ -1816,7 +1816,7 @@ POIの更新は利用できません 上に移動 下に移動 ナビゲーションの終了 - 使用しない道路として指定 + 避ける道を指定 選択したデータ保存フォルダーが書き込み保護されているため、内部メモリに切り替えました。書き込み可能な保存用ディレクトリを選択してください。 共有記憶域 より詳細なレポートは以下サイトにて @@ -3010,9 +3010,9 @@ POIの更新は利用できません アイコン選択 基本プロファイル アイコン - 最低速度 - 最高速度 - 標準移動速度 + 予想最低速度 + 予想最高速度 + 予想標準速度 すべての道路の移動速度を制限し、種別や制限速度が不明な道路が多い場合の到着時間予測に役立ちます(ルート計算に影響します) オフロード プロファイルの個別設定 @@ -3590,7 +3590,7 @@ POIの更新は利用できません POIの作成/編集 駐車位置 お気に入りの追加/編集 - アイテム順序をデフォルトに戻す + アイテム順序を初期状態に戻します 編集に戻る 選択したプロファイルを切り替えるボタンです。 プロファイルの追加 @@ -3667,12 +3667,12 @@ POIの更新は利用できません コンテキストメニュー 項目の並べ替えや非表示するものを指定できます。 分割 - 分割線で区切られた部分より下にある項目が適用されます。 + ここで指定された項目は、区切り線より下に配置されます。 非表示 これらの項目はメニューに表示されなくなりますが、オプションやプラグインはそのまま機能します。 項目 設定を非表示にすると、元の状態にリセットされます。 - ボタンは4つしかありません。 + ボタンの数は4つ固定で変更できません。 主要機能 “%1$s”ボタンをタップすると、これらの機能にアクセスできます。 アイテムはこのカテゴリ内でのみ移動できます。 @@ -3794,7 +3794,7 @@ POIの更新は利用できません \n \n国の法律に基づいて、使用を望むかどうかを決定する必要があります。 \n -\n%1$sを選択すると、スピードカメラに関するアラートと警告が表示されます。 +\n%1$sを選択すると、スピードカメラに関するアラートと警告機能を使用できます。 \n \n%2$sを選択すると、スピードカメラに関するすべてのデータ(警告、通知、POI)が、OsmAndの再インストールを行うまで削除されます。 機能を維持 From a5bc166b1c96f919fba65a621f63a3b580926af2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 1 Oct 2020 17:17:00 +0000 Subject: [PATCH 22/75] Translated using Weblate (German) Currently translated at 99.9% (3486 of 3487 strings) --- OsmAnd/res/values-de/strings.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index 319e1c4f6f..6d0aeabcd6 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -3876,9 +3876,7 @@ Track Datei zum Folgen auswählen, oder vom Gerät importieren. Die GPX-Aufzeichnung wird angehalten, wenn OsmAnd beendet wird (über „zuletzt verwendete Apps“). (Die Hintergrunddienst-Anzeige verschwindet aus der Android-Benachrichtigungsleiste.) Aufzeichnungsintervall für die generelle Track-Aufzeichnung festlegen (via Schaltfläche \'GPX\' auf dem Kartenbildschirm). - Um diese Option nutzen zu können, muss OsmAnd den Track auf die Straßen der Karte einrasten. -\n -\n Wählen Sie im nächsten Schritt ein Navigationsprofil um festzulegen, welche Straßentypen verwendet werden sollen, und wählen Sie einen Wert für die maximal zulässige Entfernung zwischen Track und Straße. + Als nächstes können Sie Ihren Track mit einem Ihrer Navigationsprofile auf die nächstgelegene erlaubte Straße einrasten lassen, um diese Option zu nutzen. Track-Wegpunkt hinzufügen %s Track Dateien ausgewählt Nur die Routenlinie wird gespeichert, die Wegpunkte werden gelöscht. From 3b0d1e469e85e8492e2e7e78896974d81f1e0a8e Mon Sep 17 00:00:00 2001 From: Evgenii Martynenko Date: Fri, 2 Oct 2020 16:01:48 +0000 Subject: [PATCH 23/75] Translated using Weblate (Russian) Currently translated at 100.0% (3487 of 3487 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 8dcc9c13c0..13d5ddd215 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -3586,7 +3586,7 @@ Подробнее об уклонах можно прочитать в %1$s. Затенение рельефа Для отображения склонов, вершин и низменностей используются тёмные тени. - Для отображения склонов требуются дополнительные карты. + Для отображения уклонов требуются дополнительные карты. Уклоны Заменить этой точкой другую. Изменения применены к профилю «%1$s». From 4d317a54d38669d9f9afdeed320ca1348afdba5f Mon Sep 17 00:00:00 2001 From: Mirco Zorzo Date: Thu, 1 Oct 2020 06:47:35 +0000 Subject: [PATCH 24/75] Translated using Weblate (Italian) Currently translated at 90.2% (3146 of 3487 strings) --- OsmAnd/res/values-it/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-it/strings.xml b/OsmAnd/res/values-it/strings.xml index e806eff554..653874de7d 100644 --- a/OsmAnd/res/values-it/strings.xml +++ b/OsmAnd/res/values-it/strings.xml @@ -1954,7 +1954,7 @@ Navigazione OsmAnd Live Livello della batteria Ungherese (formale) - Tracciato attuale + Traccia attuale Cambia posizione del marcatore Spagnolo americano Asturiano @@ -3620,7 +3620,7 @@ Mappa mondiale generale (dettagliata) Mappe extra Azione non supportata %1$s - OsmAnd tracker + Tracker OsmAnd OsmAnd + Mapillary Azione veloce Righello radiale @@ -3900,7 +3900,7 @@ \n \n Traccia semplificata - Ultimo modificato + Cronologico Nome: Z – A Nome: A – Z \ No newline at end of file From 46c2458b182075fd88f0f506ea53fa77bce04e91 Mon Sep 17 00:00:00 2001 From: Roberto GEB Date: Wed, 30 Sep 2020 06:33:38 +0000 Subject: [PATCH 25/75] Translated using Weblate (Spanish) Currently translated at 99.0% (3453 of 3487 strings) --- OsmAnd/res/values-es/strings.xml | 98 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/OsmAnd/res/values-es/strings.xml b/OsmAnd/res/values-es/strings.xml index caf2e2ec40..7b571077f7 100644 --- a/OsmAnd/res/values-es/strings.xml +++ b/OsmAnd/res/values-es/strings.xml @@ -23,7 +23,7 @@ Borrar edición Edición asíncrona OSM: PDI/Notas de OSM guardados en el dispositivo - Muestra y gestiona PDI/notas de OSM guardadas en la base de datos del dispositivo. + Muestra y gestiona PDI/Notas de OSM en la base de datos de tu dispositivo. Indica el intervalo del seguimiento en línea. Intervalo del seguimiento en línea Indica la dirección web con sintaxis de parámetros : lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}. @@ -163,7 +163,7 @@ El idioma elegido es incompatible con el motor TTS (texto a voz) instalado en Android, se usará el idioma TTS predefinido. ¿Buscar otro motor TTS en la tienda de aplicaciones\? Faltan datos ¿Ir a la tienda de aplicaciones para descargar el idioma elegido? - Invertir la dirección GPX + Invertir la dirección de la traza Usar destino actual Pasar a lo largo de la traza completa Mapa vectorial presente para esta ubicación. @@ -265,10 +265,7 @@ Todos los datos sin conexión en la versión vieja de OsmAnd son compatibles con la nueva versión, pero los puntos de Favoritos deben exportarse desde la versión vieja y luego, importarse en la nueva. Compilación {0} instalada ({1}). Descargando compilación… - ¿Instalar OsmAnd\? -\nVersión: {0} -\nFecha: {1} -\nTamaño: {2} MB + ¿Instalar OsmAnd - {0} de {1} {2} MB\? Error al recuperar la lista de compilaciones de OsmAnd Cargando compilaciones de OsmAnd… Instalar compilación de OsmAnd @@ -441,7 +438,7 @@ Búsqueda sin conexión Búsqueda en línea Máximo zoom en línea - No buscar en las teselas de mapas en línea para niveles de zoom más allá de esto. + No busca en los mapas en línea niveles de zoom más allá de éste. Distancia total %1$s, tiempo de viaje %2$d h %3$d min. Servicios de navegación con o sin conexión. Servicio de navegación @@ -606,7 +603,7 @@ Para países donde la gente conduce por el lado izquierdo del camino. Punto de partida aún no determinado. ¿Cancelar la descarga\? - El mapa base necesario para proporcionar la funcionalidad básica, está en la cola de descarga. + El mapa base necesario para proporcionar funcionalidad básica está en la cola de descargas. Activa el complemento «Mapas en línea», para elegir diferentes fuentes de mapas Mapas en línea y teselas Usa mapas en línea (descarga y guarda teselas en la tarjeta de memoria). @@ -729,8 +726,8 @@ PM AM Lugar de aparcamiento - Este complemento, registra dónde se ha aparcado el automóvil y cuánto tiempo queda (si hay un límite de tiempo). -\nTanto la ubicación como el tiempo del aparcamiento se muestran en el menú principal y en un widget sobre el mapa. Puedes añadir una notificación al calendario, en el caso de que desees tener un recordatorio al respecto. + Te permite egistrar dónde has aparcado el automóvil y cuánto tiempo queda (si hay un límite de tiempo). +\nTanto la ubicación como el tiempo del aparcamiento se muestran en el menú principal y en un control sobre el mapa. Puedes añadir un recordatorio al calendario, de Android. Aparcamiento Marcar como aparcamiento Quitar marcador de aparcamiento @@ -890,7 +887,7 @@ Curvas de nivel Otros mapas Curvas de nivel - Este complemento, proporciona la funcionalidad para tomar notas de audio, fotografía y/o video durante un viaje, usando un botón en el mapa, o directamente en el menú contextual para cualquier ubicación en el mapa. + Tomar notas de audio/foto/video durante un viaje, usando un botón de mapa o un menú contextual de la ubicación. Notas audio/vídeo Complemento OsmAnd para curvas de nivel sin conexión Este complemento proporciona una capa superpuesta de curvas de nivel y una capa (de relieve) sombreada, que se pueden visualizar sobre los mapas descargados de OsmAnd. Esta funcionalidad será muy apreciada por atletas, caminantes, excursionistas, y cualquiera interesado en la estructura de relieve de un paisaje. @@ -930,7 +927,7 @@ Grabando %1$s %3$s %2$s Por favor, considera pagar por el complemento «Curvas de nivel» para apoyar desarrollos adicionales. Complemento de curvas de nivel - El complemento de Dropbox, permite sincronizar trazas y notas multimedia con tu cuenta de Dropbox. + Sincroniza trazas y notas multimedia con tu cuenta de Dropbox. Complemento Dropbox Cambiar orden Mostrar @@ -1003,8 +1000,8 @@ Punto Nombre del archivo GPX Archivo GPX guardado en {0} - Este complemento proporciona un widget en el mapa, permitiendo crear caminos pulsando el mapa, usando o modificando archivos GPX existentes, para planificar un viaje y medir la distancia entre puntos. Los resultados pueden guardarse como un archivo GPX y usarse luego para la orientación. - Distancias y planificación + Crea caminos pulsando en el mapa, o usando o modificando archivos GPX existentes, para planificar un viaje y medir la distancia entre puntos. El resultado puede guardarse como un archivo GPX y usarse luego para la orientación. + Calculadora de distancias y herramienta de planificación * Pulse para marcar un punto. \n * Mantenga pulsado el mapa para quitar el punto anterior. \n * Mantenga pulsado en un punto para ver e incluir la descripción. @@ -1307,9 +1304,9 @@ Duración Distancia Grabación de viaje - Este complemento activa la funcionalidad para registrar y guardar tus trazas manualmente pulsando el widget de grabación GPX en el mapa, o automáticamente registrando todas tus rutas navegadas en un archivo GPX. -\n -\nLas trazas grabadas pueden ser compartidas con sus amigos o ser usadas para contribuir a OSM. Los atletas pueden usar las trazas grabadas para seguir sus entrenamientos. Algunos análisis básicos de trazas se pueden realizar directamente en OsmAnd, como tiempos por vuelta, velocidad media, etc., y por supuesto las trazas pueden analizarse posteriormente con herramientas de análisis de terceros. + Este complemento activa la funcionalidad para registrar y guardar tus trazas manualmente pulsando el widget de grabación GPX en el mapa, o automáticamente registrando todas tus rutas navegadas en un archivo GPX. +\n +\nLas trazas grabadas pueden ser compartidas con tus amigos o ser usadas para contribuir a OSM. Los atletas pueden usar las trazas grabadas para monitorizar sus entrenamientos. Algunos análisis básicos de trazas pueden realizarse directamente en OsmAnd, como tiempos por vuelta, velocidad media, etc., y por supuesto las trazas pueden analizarse posteriormente con herramientas de análisis de terceros. Rutas de autobús, trolebús y lanzadera Intervalo de registro Registra la ubicación en un archivo GPX, pudiendo des/activarlo usando el widget de grabación GPX en el mapa. @@ -1433,7 +1430,7 @@ Pista de entrenamiento Sólo caminos Compartir nota - Notas + Notas A/V Mapa en línea Exportar Audio @@ -1781,7 +1778,7 @@ Se ofrece la opción de controlar la aplicación principalmente a través del panel de control flexible o de un menú estático. Se puede cambiar esto luego, en los ajustes del panel. Usar panel de control Usar menú - El botón del menú, muestra el panel de control en lugar del menú + El botón del menú muestra el panel de control en lugar del menú Acceso desde el mapa Indica el tipo de PDI correcto u omítelo. Sin escaleras @@ -1880,7 +1877,7 @@ \nSe utiliza {3} MB temporalmente, {1} MB constantemente. (De {2} MB.) Elegir marcador del mapa Otros marcadores - Subir notas de OSM anónimas o usar el perfil de OpenStreetMap.org. + Sube tu nota de OSM de forma anónima o usando tu perfil de OpenStreetMap.org. Subir nota(s) de OSM Subir anónimamente Barra superior @@ -1910,7 +1907,7 @@ \nParte de los ingresos vuelven a la comunidad de OSM y se paga por cada contribución OSM. \nSi amas a OsmAnd, OSM y quieres apoyarlos y ser apoyado por ellos, esta es una perfecta manera de hacerlo. Mostrar barra de transparencia en el mapa - Se ha cambiado a la memoria interna, porque la carpeta de almacenamiento de datos elegida es de sólo lectura. Elige un directorio de almacenamiento válido. + Se ha cambiado a la memoria interna, porque la carpeta de almacenamiento de datos elegida está protegida de escritura. Elige un directorio de almacenamiento válido. Memoria compartida La aplicación ya permite escribir en el almacenamiento externo, pero se debe reiniciar la aplicación. Subir ↑ @@ -1918,7 +1915,7 @@ Finalizar navegación Evitar camino Informe completo - Nombre de usuario y contraseña de OpenStreetMap + Nombre de usuario y contraseña de OSM Informe Añade marcadores a través del mapa No se encontraron puntos de referencia @@ -2017,8 +2014,8 @@ Omite la búsqueda de nuevas versiones o descuentos relacionados con OsmAnd. Ocultar nuevas versiones Suscripción mensual. Puede cancelarlo en cualquier momento en Google Play. - Donaciones a la comunidad de OpenStreetMap - Parte de tu donación se envía a usuarios que realicen cambios en OpenStreetMap. El costo de la suscripción sigue siendo la misma. + Donación a la comunidad de OSM + Parte de tu donación se envía a los contribuidores a OSM. El coste de la suscripción sigue siendo el mismo. La suscripción permite actualizaciones cada hora, día o semana y descargas ilimitadas para los mapas de todo el mundo. Obtener Obtener por %1$s @@ -2121,7 +2118,7 @@ Un botón que añade una nota fotográfica en el centro de la pantalla. Un botón que añade una nota de OSM en el centro de la pantalla. Un botón que añade un PDI en el centro de la pantalla. - Un botón que des/activa las indicaciones por voz durante la navegación. + Un botón que activa o desactiva las indicaciones por voz durante la navegación. Un botón que añade la ubicación del aparcamiento en el centro de la pantalla. Mostrar un diálogo temporal " guardado como " @@ -2188,7 +2185,7 @@ \nProporciona un código completo OLC completo y válido. \nÁrea representada: %1$s x %2$s - Un botón que muestra la siguiente lista. + Un botón para paginar a través de la lista de abajo. Mapa superpuesto cambiado a «%s». Mapa subyacente cambiado a «%s». Pendiente @@ -2286,15 +2283,15 @@ \n ¡Más países alrededor del globo están disponibles para descargar! \n Obtén un navegador confiable en tu país - ya sea Francia, Alemania, México, Reino Unido, España, Países bajos, Estados Unidos, Rusia, Brasil o cualquier otro. Alternar zoom automático del mapa - Un botón que des/activa el zoom automático del mapa de acuerdo a la velocidad. - Activar zoom automático del mapa - Desactivar zoom automático del mapa + Botón para activar o desactivar el zoom automático controlado por la velocidad. + Activar zoom automático + Desactivar zoom automático Definir destino Reemplazar destino Añadir primer destino intermedio - Un botón que añade el destino de la ruta en el centro de la pantalla, cualquier destino previamente elegido se convierte en el último destino intermedio. - Este botón de acción, añade un nuevo destino de ruta en el centro de la pantalla, reemplazando el anterior destino (si existe). - Un botón que añade el primer destino intermedio en el centro de la pantalla. + Un botón que añade el centro de la pantalla como destino de la ruta, cualquier destino previamente elegido se convierte en el último destino intermedio. + Un botón que añade el centro de la pantalla como destino de la nueva ruta, reemplazando el anterior destino (si existe). + Un botón que añade el centro de la pantalla como el primer destino intermedio. Sin superposición Sin subyacencia Error @@ -2327,13 +2324,14 @@ Ubicación propia animada Activa el desplazamiento animado del mapa para «Mi ubicación» durante la navegación. Resumen - Navegación GPS + Navegación \n • Funciona en línea (rápido) o sin conexión (sin cargos de roaming al viajar al extranjero) \n • Guía por voz giro-a-giro (voces grabadas y sintetizadas) -\n • (Opcional) Guía de carriles, nombres de calles y tiempo estimado al destino -\n • Soporta puntos intermedios en el itinerario +\n • (Opcional) Guía de carriles, nombres de calles y tiempo estimado de llegada +\n • Soporta puntos intermedios en tu itinerario \n • La ruta se recalcula al salirse de la misma -\n • Busca destinos por dirección, por tipo (por ejemplo: Restaurantes, hoteles, gasolineras, museos), o por coordenada geográfica +\n • Busca lugares por dirección, por tipo (por ejemplo: Restaurantes, hoteles, gasolineras, museos), o por coordenadas geográficas +\n Vista del mapa \n • Muestra tu ubicación y orientación \n • (Opcional) Ajusta el mapa a la dirección del movimiento (o la brújula) @@ -2397,7 +2395,7 @@ Mostrar u ocultar notas de OSM Mostrar notas de OSM Ocultar notas de OSM - Un botón que muestra u oculta las notas de OSM en el mapa. + Botón para mostrar u ocultar las notas de OSM en el mapa. Ordenados por distancia Buscar en Favoritos Ocultar desde el nivel de zoom @@ -2449,10 +2447,10 @@ Min/Máx Rosa translúcido Pausar/reanudar navegación - Este botón de acción, pausa o reanuda la navegación. + Botón para pausar o reanudar navegación. Mostrar diálogo «Navegación finalizada» Iniciar/parar navegación - Este botón de acción, inicia o para la navegación. + Botón para iniciar o terminar la navegación. Tiempo del búfer para el seguimiento en línea Indica el tiempo que el búfer mantendrá los lugares para enviar sin conexión Añadir al menos un punto. @@ -2468,7 +2466,7 @@ Punto de ruta 1 Punto de referencia 1 Sin animaciones - Desactiva las animaciones en la aplicación. + Desactiva las animaciones de los mapas. Mantener en el mapa ¿Salir sin guardar? Línea @@ -2492,7 +2490,7 @@ Mover todo al historial Indicación de distancia Ordenar por - Elige cómo se indica la distancia y dirección a los marcadores del mapa en la pantalla del mapa: + Elige cómo se indica la distancia y dirección a los marcadores del mapa en el mapa: Umbral de orientación del mapa Velocidad a partir de la cual la orientación del mapa cambia de «Dirección del movimiento» a «Dirección de la brújula». Todos los marcadores del mapa movidos al historial @@ -2852,7 +2850,7 @@ Guaraní Está utilizando el mapa «{0}» que funciona con OsmAnd. ¿Quiere ejecutar la versión completa de OsmAnd\? ¿Ejecutar OsmAnd\? - Un botón que alterna entre el modo diurno y nocturno para OsmAnd. + Un botón que alterna entre los modos diurno y nocturno para OsmAnd. Modo diurno Modo nocturno Alternar modos diurno/nocturno @@ -3103,7 +3101,7 @@ Elegir el tipo de navegación Automóvil, camión, motocicleta Bicicleta de montaña, ciclomotor, caballo - Caminata, senderismo, correr + Caminata, senderismo, carrera Tipos de transporte público Barco, remo, vela Avión, ala delta @@ -3139,7 +3137,7 @@ Dificultad preferida Preferir rutas de esta dificultad, aunque el trazado sobre pistas más duras o más fáciles sigue siendo posible si son más cortas. Fuera de pista - Los senderos libres y fuera de pista son rutas y pasajes no oficiales. Típicamente descuidados, no mantenidos por los oficiales y no controlados por la noche. Entrar bajo su propio riesgo. + Los senderos libres y fuera de pista son rutas y pasajes no oficiales. Típicamente descuidados, no mantenidos y no controlados por la noche. Entra bajo tu propio riesgo. Geocodificación Error Todo terreno @@ -3361,7 +3359,7 @@ El nombre de archivo está vacío Traza guardada Revertir - Un botón para centrar en la pantalla el punto de partida y calcular la ruta hacia el destino o abre un cuadro de diálogo para elegir el destino si el marcador no está en el mapa. + Un botón que añade el centro de la pantalla como punto de partida. Pedirá luego que se fije el destino o iniciará el cálculo de la ruta. Mostrar nodo de la red de rutas ciclistas ¿Borrar %1$s\? Diálogo de descarga del mapa @@ -3394,7 +3392,7 @@ Elegir el color Los perfiles predefinidos de OsmAnd no pueden borrarse , pero sí desactivarse (en la pantalla anterior), o moverse a la parte inferior. Editar perfiles - El \'tipo de navegación\' domina como se calculan las ruta. + El \'Tipo de navegación\' determina cómo se calculan las rutas. Apariencia del perfil Icono, color y nombre Editar lista de perfiles @@ -3679,7 +3677,7 @@ Buscar tipos de PDI Acción %1$s no admitida Mapa general del mundo (detallado) - Tipo no admitido + Tipo no soportado Proporciona la anchura de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos anchos. Proporciona la altura de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos altos. Proporciona el peso de tu vehículo, algunas restricciones de ruta pueden aplicarse para vehículos pesados. @@ -3715,7 +3713,7 @@ Elige cómo se guardarán las teselas descargadas. Puede exportar o importar acciones rápidas con perfiles de aplicación. ¿Eliminar todo\? - ¿Estás seguro de que deseas eliminar irrevocablemente% d acciones rápidas\? + ¿Estás seguro deseas eliminar de forma irreversible %d acciones rápidas\? Tiempo de apagado de la pantalla Si \"%1$s\" está encendido, el tiempo de actividad dependerá de ello. metros @@ -3740,7 +3738,7 @@ Urdu Tayiko Bávaro - Rastreador OsmAnd + Trazador OsmAnd La guía para la simbología del mapa. Posiciones de estacionamiento Deshabilitado. Requiere \'Mantener la pantalla encendida\' dentro de \'Tiempo de espera después de la activación\'. @@ -3897,4 +3895,4 @@ Último modificado Nombre: Z – A Nombre: A - Z - + \ No newline at end of file From 1ef8c4e7a5407d3448c042de3714243c4e8112cc Mon Sep 17 00:00:00 2001 From: Ole Carlsen Date: Tue, 29 Sep 2020 18:28:03 +0000 Subject: [PATCH 26/75] Translated using Weblate (Danish) Currently translated at 93.7% (3270 of 3487 strings) --- OsmAnd/res/values-da/strings.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-da/strings.xml b/OsmAnd/res/values-da/strings.xml index 058b72d771..0ef92f7051 100644 --- a/OsmAnd/res/values-da/strings.xml +++ b/OsmAnd/res/values-da/strings.xml @@ -3756,8 +3756,8 @@ Slet adresse Tilføj adresse Angiv adresse - Trim før - Trim efter + Trimme før + Trimme efter Skift rutetype før Skift rutetype efter Forenklet spor @@ -3771,4 +3771,10 @@ er gemt Tilføj mindst to punkter. Omgøre + Navigere fra position til sporet + Punkt på sporet for at navigere + %s spor filer valgt + Sidst ændret + Navn: Z – A + Navn: A – Z \ No newline at end of file From 10c81a2347495d8830caaccb324c9223e5972b18 Mon Sep 17 00:00:00 2001 From: nasr pen Date: Wed, 30 Sep 2020 09:29:21 +0000 Subject: [PATCH 27/75] Translated using Weblate (Arabic) Currently translated at 100.0% (3487 of 3487 strings) --- OsmAnd/res/values-ar/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index f3bbf28ac6..68da535b68 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -721,9 +721,9 @@ عكس اتجاه المسار استخدم الوجهة الحالية يمر على طول المسار باكمله - خريطة التنقل متوفرة حالياً لهذا الموقع. + خريطة التنقل متوفرة لهذا الموقع فعلها عبر \n -\nلتفعليها \'القائمة\' ← \'ضبط الخريطة\' ← \'مصدر الخريطة\' ← \'الخريطة المحملة\'. +\n\'القائمة\' ← \'ضبط الخريطة\' ← \'مصدر الخريطة\' ← \'الخريطة المحملة\'. مصدر التوجيه الصوتي اختيار قناة لتشغيل التوجيه الصوتي. صوت المكالمة الهاتفية ( كما يحاول قطع ستريو بلوتوث السيارة ) @@ -1972,7 +1972,7 @@ بطاقة الذاكرة غير متاحة. \nلن تكون قادرا على رؤية الخرائط أو العثور على أماكن. بطاقة الذاكرة في وضع القراءة فقط. -\n يمكنك فقط مشاهدة الخريطة المحملة مسبقا ولا يمكنك التحميل من الإنترنت. +\n يمكنك فقط مشاهدة الخريطة المحملة مسبقاً ولا يمكنك التحميل من الإنترنت. انعطف يميناً بشكل حاد انعطف يساراً بشكل حاد قم بالدوران وواصل From 6cfd150555f1f5fb40dc5367ae641727acec29ad Mon Sep 17 00:00:00 2001 From: Ahmad Alfrhood Date: Mon, 28 Sep 2020 16:54:01 +0000 Subject: [PATCH 28/75] Translated using Weblate (Arabic) Currently translated at 100.0% (3487 of 3487 strings) --- OsmAnd/res/values-ar/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index 68da535b68..bd631a4b89 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -1645,7 +1645,7 @@ الطريق محظور تحديد اعكس نقطة الانطلاق والوصول - أيقونات POI + أيقونات نقاط الاهتمام النوع غير محدد قسم مسجل From 92f4bce1756a3210fd6554ba96f896af6de72a08 Mon Sep 17 00:00:00 2001 From: iman Date: Thu, 1 Oct 2020 14:15:53 +0000 Subject: [PATCH 29/75] Translated using Weblate (Persian) Currently translated at 99.9% (3485 of 3487 strings) --- OsmAnd/res/values-fa/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-fa/strings.xml b/OsmAnd/res/values-fa/strings.xml index 4d8d47c875..3a12b3accf 100644 --- a/OsmAnd/res/values-fa/strings.xml +++ b/OsmAnd/res/values-fa/strings.xml @@ -3553,7 +3553,7 @@ پروفایل سفارشی زاویه: ‎%s° زاویه - تا مسیریابی مجدد انجام شود، از موقعیت من تا مسیر محاسبه‌شده پاره‌خط مستقیمی نمایش داده می‌شود + تا مسیریابی مجدد انجام نشده، از موقعیت من تا مسیر محاسبه‌شده پاره‌خط مستقیمی نمایش داده می‌شود کمترین زاویه میان موقعیت من و مسیر آماده‌سازی چیزی انتخاب نشده @@ -3873,7 +3873,7 @@ ذخیره به‌عنوان رد جدید برعکس‌کردن مسیر تمام رد با استفاده از پروفایل انتخابی بازمحاسبه خواهد شد. - با استفاده از پروفایل انتخابی فقط پارهٔ بعدی بازمحاسبه خواهد شد. + فقط پارهٔ بعدی با استفاده از پروفایل انتخابی بازمحاسبه خواهد شد. همهٔ پاره‌های بعدی پارهٔ قبلی همهٔ پاره‌های قبلی From 14cc2f8196a06131e41c48e301fe6d361641a89c Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Thu, 1 Oct 2020 10:50:15 +0000 Subject: [PATCH 30/75] Translated using Weblate (Persian) Currently translated at 99.9% (3485 of 3487 strings) --- OsmAnd/res/values-fa/strings.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-fa/strings.xml b/OsmAnd/res/values-fa/strings.xml index 3a12b3accf..68b4a4511d 100644 --- a/OsmAnd/res/values-fa/strings.xml +++ b/OsmAnd/res/values-fa/strings.xml @@ -3278,7 +3278,7 @@ تور اسکی مسیرها برای تور اسکی. کمپر - ون کمپر + ون کمپر (RV) واگن کامیون پیک‌آپ تحلیل‌ها @@ -3928,4 +3928,9 @@ بازهٔ زمانی برای ضبط رد را انتخاب کنید (که از طریق ابزار ضبط سفر روی نقشه فعال می‌شود). نگه‌داشتن ضبط سفر ازسرگیری ضبط سفر + اسکیت این‌لاین + موتور پرشی + اسکوتر موتوری + ویلچر رو به جلو + فاصله آستانه \ No newline at end of file From d2e0d88d88646b0787953d507bd3046bc4894d9a Mon Sep 17 00:00:00 2001 From: Atrate Date: Wed, 30 Sep 2020 08:12:42 +0000 Subject: [PATCH 31/75] Translated using Weblate (Polish) Currently translated at 99.5% (3806 of 3824 strings) --- OsmAnd/res/values-pl/phrases.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-pl/phrases.xml b/OsmAnd/res/values-pl/phrases.xml index 4b3efcc10c..faec609ff4 100644 --- a/OsmAnd/res/values-pl/phrases.xml +++ b/OsmAnd/res/values-pl/phrases.xml @@ -3812,7 +3812,7 @@ Skontrastowane Uzupełnianie wody pitnej: woda z sieci Uzupełnianie wody pitnej: nie - Uzupełnianie wody pitnej: tak + Tak Poziom wody: utrzymujący się na powierzchni Poziom wody: poniżej średniego poziomu wody Poziom wody: obmywający falami @@ -3834,4 +3834,7 @@ Stan pompy: brak wiązki Strzałka: nie Winda + Małogabarytowe urządzenia elektryczne + Tablica odjazdów/odlotów + Uzupełnianie wody pitnej \ No newline at end of file From ca51a273fc001c2a067f8803994f7f33af30fc07 Mon Sep 17 00:00:00 2001 From: Hinagiku Zeppeki Date: Wed, 30 Sep 2020 23:08:29 +0000 Subject: [PATCH 32/75] Translated using Weblate (Japanese) Currently translated at 99.7% (3816 of 3824 strings) --- OsmAnd/res/values-ja/phrases.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ja/phrases.xml b/OsmAnd/res/values-ja/phrases.xml index 680da76205..66c5cc2a31 100644 --- a/OsmAnd/res/values-ja/phrases.xml +++ b/OsmAnd/res/values-ja/phrases.xml @@ -1258,7 +1258,7 @@ 有り 掃除機:無し 掃除機 - アドブルー・尿素水還元剤 + ディーゼル排気用液(AdBlue・尿素水) ドライブスルー 有り 無し @@ -3822,4 +3822,16 @@ 吸引 加圧 地下水 + ナッツ専門店 + 養蜂箱 + リアルタイム時刻表 + 一般的な時刻表 + 大まかな時刻表 + 有り + 時刻表:無し + エレベーター + 街区 + 行政区 + ギブボックス(提供品置場) + 簡易給水栓 \ No newline at end of file From 5699c465f360cf36be87ad4806b26bd6c3da6987 Mon Sep 17 00:00:00 2001 From: Vincent Bergeot Date: Tue, 29 Sep 2020 12:22:29 +0000 Subject: [PATCH 33/75] Translated using Weblate (French) Currently translated at 99.7% (3813 of 3824 strings) --- OsmAnd/res/values-fr/phrases.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-fr/phrases.xml b/OsmAnd/res/values-fr/phrases.xml index 638995c1f2..91dc180070 100644 --- a/OsmAnd/res/values-fr/phrases.xml +++ b/OsmAnd/res/values-fr/phrases.xml @@ -1762,7 +1762,7 @@ Fournitures de plomberie Fournitures de bois Ancres pour vélo - Râtelier pour vélo + Arceaux pour vélo Terminal d\'informations Carte tactile Tableau d\'affichage @@ -3380,7 +3380,7 @@ Parc animalier Enceinte Parc safari - Râtelier pour vélo + Arceaux pour vélo Vélo de sport Hachoir Hors route From 43fabf896ff45dea1d3427e5e551b6b87a977798 Mon Sep 17 00:00:00 2001 From: iman Date: Thu, 1 Oct 2020 19:50:08 +0000 Subject: [PATCH 34/75] Translated using Weblate (Persian) Currently translated at 36.2% (1388 of 3824 strings) --- OsmAnd/res/values-fa/phrases.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-fa/phrases.xml b/OsmAnd/res/values-fa/phrases.xml index eba643a4f9..dd6c374db3 100644 --- a/OsmAnd/res/values-fa/phrases.xml +++ b/OsmAnd/res/values-fa/phrases.xml @@ -9,7 +9,7 @@ فروشگاه گوشت بقالی فروشگاه محصولات دامی - سبزی فروشی + میوه و سبزی‌فروشی فروشگاه غذاهای دریایی شیرینی و آجیل فروشی بستنی فروشی From 8ba231d5f07d2e69df0b3c3198c3debf33129bdf Mon Sep 17 00:00:00 2001 From: Roberto GEB Date: Tue, 29 Sep 2020 18:29:02 +0000 Subject: [PATCH 35/75] Translated using Weblate (Spanish) Currently translated at 99.9% (3823 of 3824 strings) --- OsmAnd/res/values-es/phrases.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-es/phrases.xml b/OsmAnd/res/values-es/phrases.xml index d892b94f7e..cac7b4a36d 100644 --- a/OsmAnd/res/values-es/phrases.xml +++ b/OsmAnd/res/values-es/phrases.xml @@ -1588,7 +1588,7 @@ Koshinto Placa azul Jizo - Crucero (monumento) + Cruz Cantera histórica Agregado Antimonio @@ -1825,7 +1825,7 @@ Capacitación: artes marciales Capacitación: aviación Capacitación: peluquería - Monumento + Objeto monumental Tipo: industria petrolera Tipo: Área de pozos Tipo: fábrica @@ -1868,7 +1868,7 @@ 100LL (con plomo, para aviones) Autogas (Etanol libre de plomo) Jet A-1 (diésel) - AdBlue + Líquido de escape de diesel Combustible: madera Combustible: carbón vegetal Combustible: carbón @@ -3798,7 +3798,7 @@ Tubo Red de recarga de agua potable Recarga de agua potable: no - Recarga de agua potable: sí + Obstrucción Nivel de agua: por debajo del nivel medio del agua Nivel de agua: por encima del nivel medio del agua From b8197864511806c21dc7585f2d6e163c4fe120e8 Mon Sep 17 00:00:00 2001 From: Aulo Aasmaa Date: Mon, 28 Sep 2020 19:08:29 +0000 Subject: [PATCH 36/75] Translated using Weblate (Estonian) Currently translated at 99.4% (3468 of 3487 strings) --- OsmAnd/res/values-et/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-et/strings.xml b/OsmAnd/res/values-et/strings.xml index e219936c08..c2b36454b5 100644 --- a/OsmAnd/res/values-et/strings.xml +++ b/OsmAnd/res/values-et/strings.xml @@ -3497,7 +3497,7 @@ Kohandatud värv Jätkamiseks vali tööpäevad Teekond punktide vahel - Kavanda teekonda + Kavanda teekond Lisa rajale Näita alguse ja lõpu ikoone Vali laius @@ -3762,4 +3762,6 @@ Viimati muudetud Nimi: Z – A Nimi: A – Z + Ekraani väljalülitamine + Ratastool edasi \ No newline at end of file From 94aad1004c7ee9c0f663458f05651983d5b84104 Mon Sep 17 00:00:00 2001 From: ssantos Date: Thu, 1 Oct 2020 21:55:19 +0000 Subject: [PATCH 37/75] Translated using Weblate (Portuguese) Currently translated at 100.0% (3824 of 3824 strings) --- OsmAnd/res/values-pt/phrases.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-pt/phrases.xml b/OsmAnd/res/values-pt/phrases.xml index 8d22b73dbe..e2866a9b26 100644 --- a/OsmAnd/res/values-pt/phrases.xml +++ b/OsmAnd/res/values-pt/phrases.xml @@ -436,7 +436,7 @@ Central telefônica Reciclagem Centro de reciclagem - Contêiner + Contentor Vidro Papel Roupas @@ -1775,7 +1775,7 @@ Brinquedos Sorvete Cartão SIM - Seção + Secção Memorial de guerra Placa comemorativa Estátua @@ -2400,7 +2400,7 @@ Passageiros Veículos Bicicletas - Contêineres + Contentor Veículos pesados Academia ao ar livre Hackerspace From a9aa1e5776bc6fa72a84b4bc37e445a3e5b10b3e Mon Sep 17 00:00:00 2001 From: max-klaus Date: Sat, 3 Oct 2020 12:07:18 +0300 Subject: [PATCH 38/75] Fix import --- .../plus/inapp/InAppPurchaseHelperImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index fc9b1056f6..7b0351b3a9 100644 --- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -13,6 +13,7 @@ import com.android.billingclient.api.SkuDetailsResponseListener; import net.osmand.AndroidUtils; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.util.BillingManager; @@ -106,7 +107,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { List skuInApps = new ArrayList<>(); - for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { skuInApps.add(purchase.getSku()); } for (Purchase p : purchases) { @@ -173,7 +174,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } - public void purchaseFullVersion(final Activity activity) { + public void purchaseFullVersion(@NonNull final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppCommand() { @Override @@ -199,7 +200,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } - public void purchaseDepthContours(final Activity activity) { + public void purchaseDepthContours(@NonNull final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppCommand() { @Override @@ -325,7 +326,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - InAppPurchases.InAppPurchase fullVersion = getFullVersion(); + InAppPurchase fullVersion = getFullVersion(); if (hasDetails(fullVersion.getSku())) { Purchase purchase = getPurchase(fullVersion.getSku()); SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); @@ -334,7 +335,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - InAppPurchases.InAppPurchase depthContours = getDepthContours(); + InAppPurchase depthContours = getDepthContours(); if (hasDetails(depthContours.getSku())) { Purchase purchase = getPurchase(depthContours.getSku()); SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); @@ -343,7 +344,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - InAppPurchases.InAppPurchase contourLines = getContourLines(); + InAppPurchase contourLines = getContourLines(); if (hasDetails(contourLines.getSku())) { Purchase purchase = getPurchase(contourLines.getSku()); SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); @@ -367,7 +368,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { // Do we have the live updates? boolean subscribedToLiveUpdates = false; List liveUpdatesPurchases = new ArrayList<>(); - for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { Purchase purchase = getPurchase(p.getSku()); if (purchase != null) { liveUpdatesPurchases.add(purchase); @@ -435,12 +436,12 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); } - private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { + private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { if (purchase != null) { - inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED); inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); } else { - inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED); } inAppPurchase.setPrice(skuDetails.getPrice()); inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); From ceb5758e894a0b44d04fce0a83cc3ce8bb211be6 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sat, 3 Oct 2020 12:41:53 +0000 Subject: [PATCH 39/75] Translated using Weblate (Portuguese) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-pt/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-pt/strings.xml b/OsmAnd/res/values-pt/strings.xml index d700a97de6..2772246d72 100644 --- a/OsmAnd/res/values-pt/strings.xml +++ b/OsmAnd/res/values-pt/strings.xml @@ -3906,4 +3906,5 @@ Última modificação Nome: Z – A Nome: A – Z + Ícones de início/fim \ No newline at end of file From f7de137011fb5bb40b382fcff7bbbbccc3830dfd Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sat, 3 Oct 2020 16:08:36 +0000 Subject: [PATCH 40/75] Translated using Weblate (Russian) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-ru/strings.xml | 115 ++++++++++++++++--------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 13d5ddd215..7419222d0a 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -71,7 +71,7 @@ Ближайшие города Выберите город Поиск почтового индекса - Аудио⁣заметка + Запись аудио⁣ Записать видео Фотозаметка OSM-заметка @@ -233,7 +233,7 @@ Сохранить фильтр Удалить фильтр Новый фильтр - Изменить позицию + Изменение позиции Текущий путь Навигация OsmAnd Live Уровень заряда батареи @@ -318,7 +318,7 @@ Удалить эту запись? недоступно Аудиозаметка - Видеозаметка + Запись видео Слой аудиозаписей Запись не может быть воспроизведена. Удалить запись @@ -328,7 +328,7 @@ Аудиозаметки OsmAnd-плагин для линий высот Измерение расстояний - Нажмите «Использовать местоположение…» чтобы добавить заметку к данному местоположению. + Нажмите «Использовать местоположение…» для добавления заметки к месту. Аудиозаметки Создавайте аудио-, видео- и фотозаметки в поездке, используя виджет или контекстное меню. Аудио/видеозаметки @@ -505,7 +505,7 @@ Голосовые подсказки (TTS) Голосовые подсказки (записанные) Данные POI - Голос TTS + TTS Новый поиск Размер текста для названий на карте: Размер текста @@ -726,13 +726,13 @@ Карта памяти доступна только для чтения. \nТеперь можно только просматривать предварительно загруженную карту, а не загружать новые области. Файл распаковывается… - Направо и прямо - Резко направо и прямо - Плавно направо и прямо - Налево и прямо - Резко налево и прямо - Плавно налево и прямо - Выполните разворот, затем прямо + Направо + Резко направо + Плавно направо + Налево + Резко налево + Плавно налево + Выполните разворот Двигайтесь прямо Продолжить Загрузить детальные карты регионов @@ -864,7 +864,7 @@ 3D вид Показать последние использованные POI на карте. Показывать POI - Выберите источник онлайн или кешированных тайлов карты + Выберите источник онлайн- или кешированных тайлов карт. Растровые карты Источник карты Использовать интернет @@ -901,7 +901,7 @@ Дом Пересечение улиц Обновить карту - Создать POI + POI Да Отмена Нет @@ -1163,7 +1163,7 @@ Привязываться к дорогам во время навигации. Привязка к дороге Промежуточный пункт %1$s слишком далеко от ближайшей дороги. - Достигнут промежуточный пункт + Вы прибыли в промежуточный пункт Промежуточный пункт Промежуточный пункт Конец маршрута слишком далеко от ближайшей дороги. @@ -1178,10 +1178,10 @@ Достопримечательности Последний промежуточный пункт Первый промежуточный пункт - Добавить последним промежуточным пунктом - Добавить первым промежуточным пунктом + Последний промежуточный пункт + Первый промежуточный пункт Заменить пункт назначения - Пункт назначения уже задан: + Вы уже задали пункт назначения Пункт %1$s Точки маршрута Промежуточный пункт %1$s @@ -1201,7 +1201,7 @@ Настройки аудио и видео Изменить порядок Просмотр - Сделать фото + Снимок Сделать фото Синхронизация треков и медиазаметок с вашим аккаунтом Dropbox. Плагин Dropbox @@ -1239,7 +1239,7 @@ Укажите адрес Выбор избранной Модификации OSM - Выбирать + Выбрать OsmAnd карты и навигация OsmAnd+ карты и навигация Уменьшает «шум» компаса, но добавляет инерцию. @@ -1272,10 +1272,10 @@ Резервное копирование как правка OSM высота OsmChange-файл создан за %1$s - * Нажмите, чтобы отметить точку. -\n* Удерживайте нажатие на карте, чтобы удалить предыдущую точку. -\n* Удерживайте нажатие на точке, чтобы просмотреть и добавить описание. -\n* Нажмите на виджет измерения, чтобы увидеть больше действий. + * Нажмите, чтобы отметить точку. +\n* Нажмите и удерживайте карту, чтобы удалить предыдущую точку. +\n* Удерживайте точку для просмотра и добавления описания. +\n* Нажмите на виджет измерения для других действий. Использовать магнитный датчик вместо датчика ориентации. Другие Контурные линии @@ -1301,9 +1301,9 @@ Дорожные предупреждения Очистить промежуточные пункты Оставить промежуточные пункты - К: + Назначение: Через: - От: + Отправление: Промежуточные пункты уже заданы. Названия улиц (TTS) Объявлять… @@ -1345,9 +1345,9 @@ Симуляция использования рассчитанного маршрута Симуляция использования трека GPX Без автомасштаба - Ближний план - Средний план - Дальний план + К ближнему плану + К среднему плану + К дальнему плану Избегать автомагистралей Без автомагистралей Предпочитать автомагистрали @@ -1386,7 +1386,7 @@ Задать пункт назначения Предпочтения маршрута Информация про маршрут - Добавить как новый пункт назначения + Добавить пункт назначения Использовать показанный путь для навигации? Рассчитать сегмент маршрута OsmAnd без интернета Рассчитать маршрут OsmAnd для первого и последнего сегмента маршрута @@ -1667,7 +1667,7 @@ Моё местоположение Статус GPS Точки - Место для парковки + Парковка УДАЛИТЬ ТЕГ Редактировать группу Вам необходимо подключение к сети для установки этого плагина. @@ -2036,7 +2036,7 @@ Обновить сейчас Приложение не имеет разрешения на использование SD-карты Доступные карты - Начальный пункт + Пункт отправления Не выбрано Размер хранилища Звук @@ -2134,7 +2134,7 @@ Переместить ↑ Переместить ↓ Завершить навигацию - Избегать дорог + Избегать Публичное имя Поддерживаемый регион Введите публичное имя @@ -2289,8 +2289,8 @@ Добавить фото Разрешения Онлайн-фото - Здесь нет фотографий. - Поделитесь вашим просмотром улиц через Mapillary. + Здесь нет фото. + Поделитесь своими уличными видами через Mapillary. Виджет Mapillary Позволяет быстро внести свой вклад в Mapillary. Фото с улиц онлайн для каждого. Открывайте места, взаимодействуйте, запечатлейте весь мир. @@ -2337,7 +2337,7 @@ Измерить расстояние Добавьте хотя бы одну точку. Фотография Mapillary - Улучшить фотопокрытие через Mapillary + Улучшить фотопокрытие в Mapillary Скрыть, начиная с уровня масштабирования Прозрачно-розовый Берберский @@ -2470,7 +2470,7 @@ Полноэкранный режим Отметить пройденным Файл %1$s не содержит путевых точек, импортировать его как трек? - Выберите трек, чтобы добавить в маркеры его точки. + Выберите трек, чтобы добавить в маркеры его точки. Трек путевых точек Направо Налево @@ -2520,14 +2520,14 @@ \n• Поддержка промежуточных точек маршрута \n• Запись собственного или отправка GPX трека и следование ему \n - Карта -\n• Отображает POI (точки интереса) около вас -\n• Адаптирует карту в направлении вашего движения (или компаса) -\n• Показывает, где вы находитесь и куда вы смотрите -\n• Делитесь своим расположением, чтобы друзья смогли найти вас -\n• Сохраняет ваши самые важные места в избранных -\n• Позволяет вам выбрать как отображать названия на карте: на английском, местным или с фонетическим написанием -\n• Отображает специальные онлайн-тайлы, спутниковые снимки (с Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью + Карта +\n• Отображает POI (точки интереса) вокруг вас +\n• Поворачивает карту по направлению движения (или компаса) +\n• Показывает вашу позицию и направление взгляда +\n• Делитесь вашим местоположением, чтобы вас могли найти друзья +\n• Сохраняет важные для вас места в избранных +\n• Позволяет выбрать способ отображения названий на карте: на английском, местное или фонетическое написание. +\n• Отображает специальные онлайн-тайлы, спутниковые снимки (Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью \n Катание на лыжах \n• OsmAnd-плагин лыжные карты позволяет видеть лыжные трассы с уровнем сложности и некоторой дополнительной информацией, как расположение подъёмников и других объектов. @@ -2670,7 +2670,7 @@ Текущий Добавляет промежуточную остановку Добавляет начальную остановку - Перемещает пункт назначения и создаёт промежуточную точку + Добавляет новый пункт назначения, делая выбранный ранее промежуточной точкой Показать закрытые заметки Показать/скрыть заметки OSM на карте. GPX — подходит для экспорта в JOSM и другие OSM редакторы. @@ -2802,7 +2802,7 @@ Модификация стиля по умолчанию для увеличения контраста пешеходных и велосипедных дорог. Использует старые цвета Mapnik. Получите OsmAnd Live, чтобы разблокировать все функции: ежедневные обновления карт с неограниченной загрузкой, все платные и бесплатные плагины, Википедия, Викигид и многое другое. Промежуточное время прибытия - Промежуточное время + Прибытие в промежуточный пункт Редактировать действие Пожалуйста, пришлите скриншот этого уведомления на support@osmand.net Редактировать точку @@ -2855,7 +2855,7 @@ Для продолжения дайте OsmAnd разрешение на определение местоположения. Чёрный Поиск улицы - Сначала выберите город/населённый пункт/местность + Укажите город/место/район Восстановить Маркеры, добавленные как группа избранных или путевых точек GPX и отмеченные как пройденные, останутся на карте. Если группа не активна, маркеры исчезнут с карты. Оставить пройденные маркеры на карте @@ -2917,13 +2917,13 @@ Предыдущий маршрут Сначала задайте пункт назначения Поменять - Показать больше + Ещё Отображаемые треки Показать/скрыть треки Скрыть треки Показать треки Время суток - Поворот за поворотом + По шагам Типы дорог Переключатель, чтобы показать или скрыть выбранные треки на карте. На %1$s @@ -3066,7 +3066,7 @@ Метро Лошадь Вертолёт - Вы можете добавить собственную модифицированную версию routing.xml в ..osmand/routing + Вы можете добавить свою модифицированную версию файла routing.xml в ..osmand/routing Выберите значок Лыжи Тип: %s @@ -3158,7 +3158,7 @@ Сбой Внедорожник Выбор настроек карты для профиля - Выбор настроек экрана для профиля + Настройка элементов экрана для профиля Выбор настроек навигации для профиля Выбор верхней границы изменений Использовать бесконтактный датчик (сенсорный выключатель) @@ -3233,7 +3233,7 @@ Карта во время навигации Скорость движения, размеры, масса транспортного средства Параметры транспортного средства - Голосовые оповещения происходят только во время навигации. + Голосовые инструкции работают только при навигации. Навигационные инструкции и объявления Голосовые подсказки Экранные оповещения @@ -3319,7 +3319,7 @@ «%1$s» уже существует. Перезаписать\? Не удалось экспортировать профиль. Импорт профиля - Чтобы добавить профиль, откройте его с помощью OsmAnd. + Для добавления профиля откройте файл профиля с помощью OsmAnd. %1$s импортирован. Белый Невозможно запустить механизм преобразования текста в речь. @@ -3366,7 +3366,7 @@ Маршруты, подготовленные для фристайла или катания только на коньках без классических треков. Разрешить только классические маршруты Маршруты, подготовленные только для классического стиля без конькобежных трасс. Сюда входят маршруты, подготовленные небольшим снегоходом с более свободной лыжнёй и трассами, подготовленные вручную лыжниками. - Предпочтительный уровень сложности маршрутов. Возможно использование более сложных или лёгких путей, если они короче. + Предпочтительный уровень сложности маршрутов. Более сложные или лёгкие трассы могут использоваться, если они короче. Включать на повороте Класс 1 Класс 2 @@ -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. @@ -3803,7 +3803,7 @@ Добавить к треку Для продолжения задайте рабочие дни Маршрут между точками - Составить маршрут + Составление маршрута Выберите способ разбиения: по времени или по расстоянию. Интервал между метками расстояния или времени на треке. Своё @@ -3901,4 +3901,5 @@ Последнее изменение Имя: Я - А Имя: А - Я + Значки старта и финиша \ No newline at end of file From f98649f669c2f14d4c8f8a015e2b27da79238256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Fri, 2 Oct 2020 17:22:25 +0000 Subject: [PATCH 41/75] Translated using Weblate (Turkish) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-tr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 9713a69b10..a63ed6830b 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -3859,4 +3859,5 @@ Son değiştirme İsim: Z – A İsim: A – Z + Başlangıç/bitiş simgeleri \ No newline at end of file From d088bf832448a02c45ee325a883c00d1b16d1052 Mon Sep 17 00:00:00 2001 From: ihor_ck Date: Fri, 2 Oct 2020 18:33:05 +0000 Subject: [PATCH 42/75] Translated using Weblate (Ukrainian) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 905af29b00..8e63be02be 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -3899,4 +3899,5 @@ Востаннє змінено За назвою: Я — А За назвою: А — Я + Піктограми початку/завершення \ No newline at end of file From d4dba5a489f1ba46b58339169645b1e60cc82156 Mon Sep 17 00:00:00 2001 From: Mirco Zorzo Date: Sat, 3 Oct 2020 07:56:36 +0000 Subject: [PATCH 43/75] Translated using Weblate (Italian) Currently translated at 90.2% (3149 of 3488 strings) --- OsmAnd/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-it/strings.xml b/OsmAnd/res/values-it/strings.xml index 653874de7d..5543e68444 100644 --- a/OsmAnd/res/values-it/strings.xml +++ b/OsmAnd/res/values-it/strings.xml @@ -3903,4 +3903,5 @@ Cronologico Nome: Z – A Nome: A – Z + Icona Partenza/Arrivo \ No newline at end of file From df4951406976136d922d2561a532655806e95106 Mon Sep 17 00:00:00 2001 From: Roberto GEB Date: Sat, 3 Oct 2020 14:34:44 +0000 Subject: [PATCH 44/75] Translated using Weblate (Spanish) Currently translated at 99.2% (3463 of 3488 strings) --- OsmAnd/res/values-es/strings.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/OsmAnd/res/values-es/strings.xml b/OsmAnd/res/values-es/strings.xml index 7b571077f7..6041f2a95e 100644 --- a/OsmAnd/res/values-es/strings.xml +++ b/OsmAnd/res/values-es/strings.xml @@ -2006,14 +2006,14 @@ Necesario para descargar mapas. Buscando la ubicación… Espacio libre - Almacenamiento de datos de OsmAnd (para mapas, archivos GPX, etc.): %1$s. + Almacenamiento de datos de OsmAnd (para mapas, trazas, etc.): %1$s. Conceder permiso Permitir el acceso a la ubicación Obtenga direcciones y descubra lugares nuevos, sin una conexión a Internet Encontrar mi ubicación Omite la búsqueda de nuevas versiones o descuentos relacionados con OsmAnd. Ocultar nuevas versiones - Suscripción mensual. Puede cancelarlo en cualquier momento en Google Play. + Suscripción mensual. Puedes cancelarla en cualquier momento en Google Play. Donación a la comunidad de OSM Parte de tu donación se envía a los contribuidores a OSM. El coste de la suscripción sigue siendo el mismo. La suscripción permite actualizaciones cada hora, día o semana y descargas ilimitadas para los mapas de todo el mundo. @@ -2029,8 +2029,8 @@ Subir PDI Cálculo de la ruta Ciudad o región - Sin archivos GPX aún - También puedes añadir archivos GPX a la carpeta + No tienes archivos de trazas aún + También puedes añadir archivos de trazas a la carpeta Añadir más… Aspecto Notificaciones @@ -2044,7 +2044,7 @@ Usar autopistas Permite usar autopistas. Activar la grabación rápida - Muestra una notificación del sistema que permite la grabación del viaje. + Muestra una notificación del sistema que permite empezar la grabación del viaje. Viaje Grabado Grabar @@ -2417,7 +2417,7 @@ Abrir Mapillary Instalar Mejorar cobertura de fotos con Mapillary - Instala Mapillary para añadir una o más fotos a esta ubicación del mapa. + Instala Mapillary para añadir fotos a esta ubicación del mapa. Fotos en línea Imagen de Mapillary Permisos @@ -2492,7 +2492,7 @@ Ordenar por Elige cómo se indica la distancia y dirección a los marcadores del mapa en el mapa: Umbral de orientación del mapa - Velocidad a partir de la cual la orientación del mapa cambia de «Dirección del movimiento» a «Dirección de la brújula». + Selecciona la velocidad a partir de la que la orientación del mapa cambia de «Dirección del movimiento» a «Dirección de la brújula». Todos los marcadores del mapa movidos al historial Marcador del mapa movido al historial Marcador del mapa movido a los activos @@ -2601,8 +2601,8 @@ Pulsa un marcador en el mapa para moverlo al primer lugar de los marcadores activos, sin abrir el menú contextual. Activar «Una pulsación» ¡Toma notas! - Añade una nota de audio, vídeo o foto para cada punto del mapa, utilizando el widget o el menú contextual. - Notas por fecha + Añade una nota de audio, vídeo o foto a cualquier punto del mapa, utilizando el control o el menú contextual. + Notas A/V por fecha Por fecha Por tipo Aquí hay: @@ -2954,7 +2954,7 @@ Autopista Carretera/ruta estatal Carretera principal - Calle residencial + Calle Vía de servicio Acera Camino rural @@ -3847,7 +3847,7 @@ Distancia umbral Perfil de navegación Selecciona un archivo de traza al que agregar el nuevo segmento. - Imágenes a pie de calle + Fotos a pie de calle ¿Estás seguro de que quieres descartar todos los cambios en la ruta planeada cerrándola\? En caso de dirección contraria Guardar como nuevo archivo de traza From 3c65a8e986bbd610f7ad19f83873c7c97add4885 Mon Sep 17 00:00:00 2001 From: ace shadow Date: Fri, 2 Oct 2020 19:37:10 +0000 Subject: [PATCH 45/75] Translated using Weblate (Slovak) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-sk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index a9fbf672f4..9c2a49a143 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3902,4 +3902,5 @@ Naposledy zmenené Názov: Z – A Názov: A – Z + Ikony štartu/cieľa \ No newline at end of file From fe68e826a5e294e2defd2675b99a53cd3680f634 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Fri, 2 Oct 2020 18:34:54 +0000 Subject: [PATCH 46/75] Translated using Weblate (Hebrew) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-iw/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 7f556c9926..8f3eb5c17c 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3909,4 +3909,5 @@ שינוי אחרון שם: ת – א שם: א – ת + סמלי התחלה/סיום \ No newline at end of file From 6fd475851662bc8ff26080c3e5d8460ab97422a3 Mon Sep 17 00:00:00 2001 From: ovl-1 Date: Sat, 3 Oct 2020 14:05:19 +0000 Subject: [PATCH 47/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 17.4% (607 of 3488 strings) --- OsmAnd/res/values-nb/strings.xml | 93 ++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index 32c34dd961..b334985f7d 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -1650,7 +1650,7 @@ Valgt valgte FJERN MERKELAPPEN - Last ned nattlige utviklerversjoner. + Last ned aktuelle utviklingsversjoner. Byggversjoner Spesifiser en mellomtjener. Innlogget som %1$s @@ -2142,7 +2142,7 @@ Vis/skjul OSM-notater Vis OSM-notater Skjul OSM-notater - Å trykke denne handlingsknappen viser eller skjuler OSM-notater på kartet. + Knapp til å vise eller skjule OSM-notater på kartet. Takk for at du kjøpte \'Havdybdekonturer\' Havdybdekonturer Havdybdekonturer @@ -2257,17 +2257,17 @@ Rediger handling Slett handling Navneforvalg - Trykk på denne handlingsknappen legger til en kartmarkør i skjermsenteret. - Trykking på denne handlingsknappen legger til et GPX-rutepunkt i midten av skjermen. - Trykking på denne handlingsknappen legger til et lydnotat i midten av skjermen. - Trykking på denne handlingsknappen legger til et videonotat i midten av skjermen. - Trykking på denne handlingsknappen legger til et bildenotat i midten av skjermen. - Trykking på denne handlingsknappen legger til et OSM-notat i midten av skjermen. - Trykking på denne handlingsknappen slår av eller på taleveiledning under navigering. - Trykking på denne handlingsknappen legger til en parkeringsplass i midten av skjermen. + En knapp for å legge til en kartmarkør i skjermsenteret. + En knapp for å legge til et GPX-rutepunkt i midten av skjermen. + En knapp for å legge til et lydnotat i midten av skjermen. + En knapp for å legge til et videonotat i midten av skjermen. + En knapp for å legge til et bildenotat i midten av skjermen. + En knapp for å legge til et OSM-notat i midten av skjermen. + En knapp til å slå av eller på taleveiledning under navigering. + En knapp for å legge til en parkeringsplass i midten av skjermen. Vis en midlertidig dialog " lagret i " - Trykking på denne handlingsknappen viser eller skjuler favorittpunktene på kartet. + En knapp til å vise eller skjule favorittpunktene på kartet. Opprett elementer La stå tomt for å bruke adressen eller stedsnavnet. Denne meldingen inkluderes i kommentarfeltet. @@ -2341,10 +2341,10 @@ Et trykk på kartet skjuler/viser kontrollknappene og miniprogrammene. Marker som passert Kunne ikke endre notatet. - Trykking på denne handlingsknappen slår av/på automatisk kartforstørrelse i henhold til hastigheten din. - Trykking på denne handlingsknappen gjør skjermsenteret til rutemål, ethvert tidligere valgt reisemål blir det siste mellomliggende målet. - Trykking på denne handlingsknappen gjør skjermsenteret det nye rutemålet, og erstatter det tidligere valgte reisemålet (hvis noe). - Trykking på denne handlingsknappen gjør skjermsenteret til det første mellomliggende reisemålet. + Knapp til å slå av eller på hastighetsbasert auto-zoom. + En knapp for å gjøre skjermsenteret til rutemålet, et tidligere valgt reisemål blir det siste mellomliggende målet. + En knapp for å gjøre skjermsenteret til det nye rutemålet, erstatter det tidligere valgte reisemålet (hvis noe). + En knapp for å gjøre skjermsenteret til det første mellomliggende reisemålet. Abonner på vår e-postliste om programrabatter og få tre kartnedlastinger til! Havdybdepunkter for sørlige halvkule Havdybdepunkter for nordlige halvkule @@ -2382,7 +2382,7 @@ Aktiver \"sjøkartvisning\" -tillegget Navnet inneholder for mange store bokstaver. Fortsett\? Legg til interessepunkt - Trykking på denne handlingsknappen viser eller skjuler interessepunkter på kartet. + En knapp til å vise eller skjule interessepunkter på kartet. En knapp til å bla gjennom listen nedenfor. Fyll ut alle parametere Ved å trykke lenge og dra knappen endres dens plassering på skjermen. @@ -2408,7 +2408,7 @@ Alle punkter i gruppen GPX-fil med de valgte notatenes koordinater og data. GPX-fil med alle notaters koordinater og data. - Trykking på denne handlingsknappen legger til et interessepunkt i midten av skjermen. + En knapp for å legge til et interessepunkt i midten av skjermen. Åpen fra Åpen til Stenger @@ -2859,10 +2859,10 @@ Du bruker {0} kart levert av OsmAnd. Vil du starte fullversjonen av OsmAnd\? Kjør OsmAnd\? Guaraní - Vekselvender mellom dag- og nattmodus i OsmAnd + En knapp til å skifte mellom dag- og nattmodus i OsmAnd. Dagmodus Nattmodus - Veksle dag-/nattmodus + Bytt dag/natt-modus Offentlig transport Sett reisemål Legg til mellomliggende @@ -3256,7 +3256,7 @@ OsmAnd-profil: %1$s Profil-import Hvit - Brukes til å estimere ankomsttid for ukjente veityper, og for å begrense farten på alle veier (kan endre rute) + Estimerer ankomsttid for ukjente veityper og begrenser farten for alle veier (kan påvirke ruting) Spor-lagringsmappe Spor kan lagres i \'rec\'-mappen, månedlige eller daglige mapper. Ta opp spor til \'rec\'-mappen @@ -3286,13 +3286,13 @@ \n \n Du kan bruke denne endringen på alle eller bare på den valgte profilen. - En bryter for å vise eller skjule koter på kartet. + Knapp som viser eller skjuler koter på kartet. Eksporter profil \"%1$s\" finnes allerede. Overskriv\? Kunne ikke eksportere profil. Legg til en profil ved å åpne profilfilen med OsmAnd. - %1$s feil under import: %2$s - %1$s ble importert. + %1$s importfeil: %2$s + %1$s importert. Bytt %1$s og %2$s Startpunkt Isvei @@ -3345,20 +3345,20 @@ Åpen plasseringskode (OLC) Valgt format vil benyttes i programmet. Denne innstillingen er valgt som forvalg for profiler: %s - Bruk kun for «%1$s» - Bruk for alle profiler + Bruk kun for \"%1$s\" + Bruk på alle profiler Analyseinstrumenter Vis kart på låseskjermen under navigering. - Innstillinger for ruting i valgt profil «%1$s». + Innstillinger for ruting i den valgte profilen \"%1$s\". Tidsavbrudd etter oppvåkning Varsler vises nede til venstre under navigering. Kart under navigasjon Kart under navigasjon Stemmekunngjøringer finner kun sted under navigasjon. Navigasjonsinstruks og kunngjøringer - Stemmekunngjøringer + Talemeldinger Sett opp ruteparameter - Ruteparameter + Ruteparametere Last ned detaljert %s-kart for å vise dette området. Slede Akebrett @@ -3398,13 +3398,13 @@ Personlig Laster ned %s Tykk - For ørkener og andre tynt befolkede områder. Høyere detaljnivå. + For ørkener og andre tynt befolkede områder. Mer detaljert. Posisjonsikon under bevegelse Posisjonsikon i hviletilstand Trykk på \'Bruk\' sletter fjernede profiler permanent. Hovedprofil Velg farge - Du kan ikke slette forvalgsprofilene, men du kan skru dem av før dette steget, eller flytte dem til bunnen. + OsmAnd-Standardprofiler kan ikke slettes, men deaktiveres (på forrige skjermbilde), eller bli sortert til bunnen. Rediger profiler Navigasjonstype har innvirkning på regler for ruteberegning. Profilutseende @@ -3418,8 +3418,8 @@ %1$s %2$s Importer profil OSM - \"%1$s\"-filen inneholder ingen ruteplanleggingsregler, velg en annen fil. - Ustøttet filtype. Du må velge en fil med %1$s-filendelse. + Ingen rutingsregler i \'%1$s\'. Velg en annen fil. + Velg en støttet fil med %1$s-endelse isteden. Importer fra fil Importer ruteplanleggingsfil Navigasjon, loggingsnøyaktighet @@ -3429,7 +3429,7 @@ Tillater deg å dele nåværende plassering ved bruk av turopptak. Nettbasert sporing Loggingsnøyaktighet - Du kan finne alle dine innspilte spor i %1$s, eller i OsmAnd-mappen. + Dine innspilte spor er i %1$s, eller i OsmAnd-mappen. Du finner alle dine OSM-notater i %1$s. Videonotater Bildenotater @@ -3601,7 +3601,7 @@ Sjekk og del detaljert loggføring fra programmet Bruk systemets skjermtidsavbrudd Programtillegg av - Ingen omregning + Ingen ny beregning Angi et navn for profilen Velg data å importere. Du kan lese mer om løyper i %1$s. @@ -3662,7 +3662,7 @@ Fotoboks-interessepunkter Avinstaller Du kan sette fartøyhøyde for å unngå lave broer. Hvis broen endrer høyde, brukes høyden i åpen tilstand. - Slett neste målpunkt + Slett nærmeste målpunkt Navngi punktet Vis/skjul Mapillary Skjul Mapillary @@ -3675,19 +3675,19 @@ Navigasjonsinstruks - + Avinstaller og start på nytt Rullestol Gokart Planlegg en rute Du får tilgang til disse handlingene ved å trykke på knappen “%1$s”. - Slå på for å stille inn zoomnivået på kartet med enhetens volumknapper. + Styr zoomnivået på kartet med enhetens volumknapper. Det tillagte punktet vil ikke være synlig på kartet, siden den valgte gruppen er skjult, du kan finne det i \"%s\". Scooter Rullestol framover - Du må definere arbeidsdagene for å fortsette + Still inn arbeidsdager for å fortsette Legg til i et spor - Vis ikoner for start-mål + Vis ikoner for start og mål Velg bredde Velg intervallet hvor markeringer med avstand eller tid på sporet vil vises. Velg det ønskede oppdelingsalternativet: etter tid eller etter avstand. @@ -3702,7 +3702,7 @@ Sist redigert Importer spor Åpne eksisterende spor - Velg en sporfil for åpning. + Velg en sporfil for å åpne. Snu rute Overskriv spor Hele sporet blir beregnet på nytt med den valgte profilen. @@ -3722,7 +3722,7 @@ Importer eller ta opp sporfiler Følg spor Velg sporfil å følge - Velg sporfil å følge, eller importer en. + Velg sporfil å følge eller importer fra enheten din. Velg et annet spor Starten av sporet Nærmeste punkt @@ -3747,4 +3747,15 @@ Sist endret Navn: Å - A Navn: A - Å + Start/mål-ikoner + Lagre som ny sporfil + Legg til i en sporfil + Spor + Spor + Spor + Turopptak + Lagre som sporfil + %s sporfiler valgt + Sett turopptak på pause + Gjenoppta turopptak \ No newline at end of file From a0afcb514c23d6c4c6d95d438c2dbb4095b0d386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 3 Oct 2020 13:04:58 +0000 Subject: [PATCH 48/75] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 17.4% (607 of 3488 strings) --- OsmAnd/res/values-nb/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index b334985f7d..9c24c2aa7c 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -751,7 +751,7 @@ Sikker modus Programmet kjører i sikker modus (skru det av i \'Innstillinger\'). Talemeldinger stopper midlertidig musikkavspilling. - Avbryt musikk + Sett musikk på pause Offentlig Optimaliser kart for Vis fra zoom-nivå (krever kotedata): @@ -3627,7 +3627,7 @@ \nSkru av ubrukte programtillegg for å skjule alle deres styringskontroller. %1$s. Disse elementene er skjult fra menyen, men de representerte valgene eller programtilleggene vil fortsette å virke. Velg språkene Wikipedia-artikler skal vises på i kartet. Du kan bytte mellom alle tilgjengelige språk mens du leser artikkelen. - Veiledning til kartets merking. + Veiledning til kartets symbolbruk. Ruteplanlegging Minsteavstand for å beregne rute på nytt OsmAnd har allerede elementer med samme navn som de i importen. From 4a22b8751703e8c227b20b50e337027c5b482294 Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Fri, 2 Oct 2020 18:30:34 +0000 Subject: [PATCH 49/75] Translated using Weblate (Sardinian) Currently translated at 99.7% (3480 of 3488 strings) --- OsmAnd/res/values-sc/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index accd1282b4..cf47d42af8 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -3903,4 +3903,5 @@ Ùrtima modìfica Nùmene: Z – A Nùmene: A – Z + Iconas de incumintzu/fine \ No newline at end of file From 2e96ff82864a252745af44e6fae010df29a1ee54 Mon Sep 17 00:00:00 2001 From: ihor_ck Date: Fri, 2 Oct 2020 18:34:44 +0000 Subject: [PATCH 50/75] Translated using Weblate (Ukrainian) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-uk/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-uk/phrases.xml b/OsmAnd/res/values-uk/phrases.xml index 4375b43720..493e631c02 100644 --- a/OsmAnd/res/values-uk/phrases.xml +++ b/OsmAnd/res/values-uk/phrases.xml @@ -3832,4 +3832,5 @@ Невеликі електроприлади Вулик Насіннєвий магазин + СПГ \ No newline at end of file From 40ac63abac08912d95c771eacca44c580b2556cf Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Fri, 2 Oct 2020 18:32:12 +0000 Subject: [PATCH 51/75] Translated using Weblate (Sardinian) Currently translated at 99.4% (3805 of 3825 strings) --- OsmAnd/res/values-sc/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-sc/phrases.xml b/OsmAnd/res/values-sc/phrases.xml index df52122c1e..62f4c1ece8 100644 --- a/OsmAnd/res/values-sc/phrases.xml +++ b/OsmAnd/res/values-sc/phrases.xml @@ -3840,4 +3840,5 @@ Eletrodomèsticos minores Tabellone de sas tzucadas Ricàrriga de abba potàbile + GNL (LNG) \ No newline at end of file From b9b2f615bd7fdfdb0701f8fc3e36039e68b61ddd Mon Sep 17 00:00:00 2001 From: ace shadow Date: Fri, 2 Oct 2020 23:24:11 +0000 Subject: [PATCH 52/75] Translated using Weblate (Slovak) Currently translated at 94.3% (3609 of 3825 strings) --- OsmAnd/res/values-sk/phrases.xml | 67 ++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/OsmAnd/res/values-sk/phrases.xml b/OsmAnd/res/values-sk/phrases.xml index 14f1a5e6be..fc275bf7c3 100644 --- a/OsmAnd/res/values-sk/phrases.xml +++ b/OsmAnd/res/values-sk/phrases.xml @@ -3274,9 +3274,9 @@ Mahájana Zamrznutí Turistická/trasová značka - - - + Ko-Shintō + Jizō + Prasat Apoštolská cirkev Radiačná onkológia Nebezpečenstvo @@ -3567,4 +3567,65 @@ Šípka Vibrácie Tlak + Skvapalnený zemný plyn + Obchod s orechmi + Včelí úľ + Cestovný poriadok + Odjazdy v reálnom čase + Intervaly + Áno + Tabuľa odjazdov: nie + Výťah + Mestský blok + Mestský obvod + Šípka: nie + Nie + Áno + Vibrovanie: nie + Výber hotovosti: cudzie karty + Výber hotovosti: minimálny nákup + Poplatok za výber hotovosti: nie + Poplatok za výber hotovosti: áno + Výber hotovosti: nie je vyžadovaný nákup + Výber hotovosti: vyžadovaný nákup + Mena výberu hotovosti + Limit výberu hotovosti + Typ výberu hotovosti: samoobslužný výber + Typ výberu hotovosti: pri pokladni + Operátor výberu hotovosti + Výber hotovosti + Výber hotovosti: áno + Zmiešané: nie + Zmiešané: áno + Ľad: nie + Ľad: áno + Trvanlivosť vodného zdroja: núdzový + Trvanlivosť vodného zdroja: trvalý + Ordinácia pôrodnej asistentky + Opatrovateľská služba + Ordinácia psychológa + Ordinácia liečiteľa + Ordinácia terapeuta + Ordinácia lekára + Zdravotná služba: testovanie: nie + Zdravotná služba: testovanie: áno + Zdravotná služba: podpora: nie + Zdravotná služba: podpora: áno + Zdravotná služba: očkovanie: nie + Zdravotná služba: očkovanie: áno + Zdravotná služba: prevencia: nie + Zdravotná služba: prevencia: áno + Zdravotná služba: starostlivosť o deti: nie + Zdravotná služba: starostlivosť o deti: áno + Zdravotná služba: vyšetrenie: nie + Zdravotná služba: vyšetrenie: áno + Zdravotná služba: poradenstvo: nie + Zdravotná služba: poradenstvo: áno + Zdravotná služba: opatrovateľstvo: áno + Zdravotná služba: opatrovateľstvo: nie + Správanie + Športová medicína + Malé elektrické spotrebiče + Tabuľa odjazdov/cestovný poriadok + Doplnenie pitnej vody \ No newline at end of file From 8856e9c4c57eb38dc6799588e263f7e83ab049a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 2 Oct 2020 21:42:44 +0000 Subject: [PATCH 53/75] Translated using Weblate (Estonian) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-et/phrases.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-et/phrases.xml b/OsmAnd/res/values-et/phrases.xml index 6d72d50576..4cb42eee67 100644 --- a/OsmAnd/res/values-et/phrases.xml +++ b/OsmAnd/res/values-et/phrases.xml @@ -524,7 +524,7 @@ Oktaan 95 Oktaan 98 Oktaan 100 - CNG + Surugaas 1:25 kütus 1:50 kütus Etanool @@ -3826,4 +3826,5 @@ Väikesed elektriseadmed Mesitaru Pähklipood + Veeldatud maagaas \ No newline at end of file From da688ccbb8d3722f1c087fc51bd2df8af7cb5063 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 3 Oct 2020 15:49:54 +0000 Subject: [PATCH 54/75] Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-es-rAR/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index 99f789d362..21492272da 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -3907,4 +3907,5 @@ Último modificado Nombre: Z – A Nombre: A – Z + Iconos de inicio/fin \ No newline at end of file From 1ffa3b7f703aac35a6585438d4274c0f7099bb7c Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 3 Oct 2020 15:51:06 +0000 Subject: [PATCH 55/75] Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-es-rAR/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-es-rAR/phrases.xml b/OsmAnd/res/values-es-rAR/phrases.xml index 4019b950b2..88c35ef6f9 100644 --- a/OsmAnd/res/values-es-rAR/phrases.xml +++ b/OsmAnd/res/values-es-rAR/phrases.xml @@ -3851,4 +3851,5 @@ Pequeños electrodomésticos Panal de abejas Frutos secos + Gas natural licuado \ No newline at end of file From c8b442fb716ab98da93c3ce1d0e7af25aa4f35ce Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Fri, 2 Oct 2020 22:30:57 +0000 Subject: [PATCH 56/75] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-pt-rBR/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index a2c4186817..3807266c57 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -3899,4 +3899,5 @@ Última modificação Nome: Z – A Nome: A – Z + Ícones de início/término \ No newline at end of file From e63863342c665444d3c40439e6201432cb8aec74 Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Fri, 2 Oct 2020 22:32:22 +0000 Subject: [PATCH 57/75] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-pt-rBR/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-pt-rBR/phrases.xml b/OsmAnd/res/values-pt-rBR/phrases.xml index dd7c249f87..07c80c478d 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -3844,4 +3844,5 @@ Pequenos aparelhos elétricos Colmeia Loja de nozes + GNL \ No newline at end of file From 1b061f6e79a9328a543ef9f2412a964e654b80ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sat, 3 Oct 2020 10:05:54 +0000 Subject: [PATCH 58/75] Translated using Weblate (Turkish) Currently translated at 66.5% (2547 of 3825 strings) --- OsmAnd/res/values-tr/phrases.xml | 125 +++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index ed3292f096..dc31464e79 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -45,7 +45,7 @@ Kullanıcı tanımlı Paleontolojik alan Fırın - Likör dükkanı + İçki dükkanı Peynir dükkanı Çikolata dükkanı Kahve dükkanı @@ -362,7 +362,7 @@ Çocuk bezi Araba aküsü Arabalar - Bisiklet + Bisikletler Depolama Çöp bertaraf Çöp tenekesi @@ -766,7 +766,7 @@ Tarımsal motorlar Demirci Bira fabrikası - Boat builder + Gemi yapımcısı Ciltçi Marangoz Halı satıcısı @@ -866,7 +866,7 @@ Gün işareti Mesafe işareti Havuz - Lezbiyen + Su seti Simgesel Yapı Deniz işareti, ışık Deniz işareti, büyük ışık @@ -2148,7 +2148,7 @@ Kostüm Geleneksel Takım elbise - Hamile + Hamilelik Nostalji Büyük beden Okul @@ -2443,4 +2443,119 @@ Tarihi tren istasyonu Tarihi çiftlik Pa (müstahkem maori yerleşimi) + Futsal (salon futbolu) + ATM: hayır + ATM: evet + Boks + Lakros + Disk iteleme oyunu + Squash (duvar tenisi) + Uzaktan kumandalı araba yarışı + Disk golf + Judo + Badminton + Karting + Netbol + Koşma + Gal oyunları + Dojo + Resmi adı + Araç park çatısı + Garaj bölmeleri + Tür: yüzey + Su ısıtıcısı: hayır + Su ısıtıcısı: evet + Mikrodalga fırın: hayır + Mikrodalga fırın: evet + Bilardo + Yüzme: hayır + Yüzme: evet + Tekne depolama + Referans numarası + Tünel numarası + Köprü numarası + Taşıma: evet + Uzunluk + Havai fişek mağazası + Elektronik onarımı + Hackerspace + Fitness istasyonu + Bina türü: piramit + Palyatif tıp + Davranış + Derinlik psikolojisi + Naturopati + Kayropraktik + Bitkisel tıp + Reiki + Geleneksel Çin tıbbı + Homeopati + Akupunktur + Yetişkin psikiyatrisi + Podoloji + Spor hekimliği + Manuel terapi + Konuşma terapisi + Klinik patoloji + Optometri + Bağımlılık tedavisi + Sağlık uzmanlığı: obstetrik (sezaryen): hayır + Obstetrik (sezaryen) + Sağlık uzmanlığı: sosyal pediatri: hayır + Sosyal pediatri + Sağlık uzmanlığı: obstetrik (doğum öncesi): hayır + Obstetrik (doğum öncesi) + Sağlık uzmanlığı: obstetrik (doğum sonrası): hayır + Obstetrik (doğum sonrası) + Sağlık uzmanlığı: tropikal tıp: hayır + Tropikal tıp + Onkoloji + Patolojik anatomi + Nükleer tıp + Endokrinoloji + Nöropsikiyatri + Beyin ve sinir cerrahisi + Nefroloji (böbrek hastalıkları) + Diş hekimliği + Gastroenteroloji + Tıbbi görüntüleme + Çene-yüz cerrahisi + Fiziksel tıp ve rehabilitasyon + Çocuk psikiyatrisi + İş terapisi + Biyokimya + Fizyoterapi + Ortodonti + Plastik cerrahi + Sağlık uzmanlığı: kaza ve acil tıp: hayır + Kaza ve acil tıp + Hamilelik + Diş, ağız ve çene-yüz cerrahisi + Göğüs hastalıkları + Anesteziyoloji + Osteopati + Klinik biyoloji + Travmatoloji + Kardiyoloji + Dermato-venereoloji + Nöroloji + Psikiyatri + Radyoterapi + Radyoloji + Genel cerrahi + Üroloji + Dermatoloji + Sağlık uzmanlığı: pediatri: hayır + Pediatri + Kulak burun boğaz + Ortopedi + İç hastalıkları + Jinekoloji + Oftalmoloji + Pratisyen doktor + Ağır yük araçları + Bisikletler + Konteynerler + Araçlar + Yolcular \ No newline at end of file From 25f8c31834efbc6784042a9445340c4389836a67 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 3 Oct 2020 15:52:25 +0000 Subject: [PATCH 59/75] Translated using Weblate (Spanish (American)) Currently translated at 100.0% (3488 of 3488 strings) --- OsmAnd/res/values-es-rUS/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-es-rUS/strings.xml b/OsmAnd/res/values-es-rUS/strings.xml index a7750a3b93..6ba4dcc29e 100644 --- a/OsmAnd/res/values-es-rUS/strings.xml +++ b/OsmAnd/res/values-es-rUS/strings.xml @@ -3904,4 +3904,5 @@ Nombre: Z – A Nombre: A – Z Último modificado + Iconos de inicio/fin \ No newline at end of file From 780adbfc7b7956d268cfd2b1762109a43a29e400 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 3 Oct 2020 15:52:09 +0000 Subject: [PATCH 60/75] Translated using Weblate (Spanish (American)) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-es-rUS/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-es-rUS/phrases.xml b/OsmAnd/res/values-es-rUS/phrases.xml index 25786f2488..8c0dbc2414 100644 --- a/OsmAnd/res/values-es-rUS/phrases.xml +++ b/OsmAnd/res/values-es-rUS/phrases.xml @@ -3851,4 +3851,5 @@ Tablero de partidas Frutos secos Panal de abejas + Gas natural licuado \ No newline at end of file From 47b4e7dd91a4be01cd49d68a00fb664f70d75f50 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sat, 3 Oct 2020 12:42:16 +0000 Subject: [PATCH 61/75] Translated using Weblate (Portuguese) Currently translated at 100.0% (3825 of 3825 strings) --- OsmAnd/res/values-pt/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-pt/phrases.xml b/OsmAnd/res/values-pt/phrases.xml index e2866a9b26..de9490478b 100644 --- a/OsmAnd/res/values-pt/phrases.xml +++ b/OsmAnd/res/values-pt/phrases.xml @@ -3829,4 +3829,5 @@ Pequenos aparelhos elétricos Colmeia Loja de nozes + GNL \ No newline at end of file From f87aa144d3a1432915c81bbd89ffa92f58c6291f Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sat, 3 Oct 2020 16:16:11 +0000 Subject: [PATCH 62/75] Translated using Weblate (Russian) Currently translated at 100.0% (3488 of 3488 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 7419222d0a..cdc7518bfc 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -126,7 +126,7 @@ Средняя скорость Время в движении Общее время - Максимальная + Макс. Отправление Прибытие Цвет From b51cab5591d4db04001453c33ed2cc743fbec9d4 Mon Sep 17 00:00:00 2001 From: vshcherb Date: Sat, 3 Oct 2020 19:28:42 +0200 Subject: [PATCH 63/75] Update SRTMPlugin.java --- OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java b/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java index 808c541523..9138e9879d 100644 --- a/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java +++ b/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java @@ -359,7 +359,7 @@ public class SRTMPlugin extends OsmandPlugin { .setTitleId(R.string.shared_string_terrain, mapActivity) .setDescription(app.getString(terrainMode == TerrainMode.HILLSHADE ? R.string.shared_string_hillshade - : R.string.shared_string_slope)) + : R.string.download_slope_maps)) .setSelected(terrainEnabled) .setColor(terrainEnabled ? R.color.osmand_orange : ContextMenuItem.INVALID_ID) .setIcon(R.drawable.ic_action_hillshade_dark) From ec53a8fc12d9b700e8412f09b041741a0a4593b9 Mon Sep 17 00:00:00 2001 From: max-klaus Date: Sat, 3 Oct 2020 20:48:55 +0300 Subject: [PATCH 64/75] Finished huawei inapps --- OsmAnd/.gitignore | 3 + OsmAnd/build.gradle | 13 +- .../plus/inapp/InAppPurchaseHelperImpl.java | 10 + .../net/osmand/plus/inapp/CipherUtil.java | 4 +- .../plus/inapp/InAppPurchaseHelperImpl.java | 291 +++++++++++------- .../osmand/plus/inapp/InAppPurchasesImpl.java | 26 +- .../osmand/plus/inapp/SubscriptionUtils.java | 3 +- .../chooseplan/ChoosePlanDialogFragment.java | 11 +- .../plus/inapp/InAppPurchaseHelper.java | 3 + .../net/osmand/plus/inapp/InAppPurchases.java | 2 +- 10 files changed, 226 insertions(+), 140 deletions(-) diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index e3071e5fbf..8bfbd5b688 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -13,10 +13,13 @@ libs/it.unibo.alice.tuprolog-tuprolog-3.2.1.jar libs/commons-codec-commons-codec-1.11.jar libs/OsmAndCore_android-0.1-SNAPSHOT.jar +# Huawei libs/huawei-*.jar huaweidrmlib/ HwDRM_SDK_* drm_strings.xml +agconnect-services.json +OsmAndHms.jks # copy_widget_icons.sh res/drawable-large/map_* diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 1232b9661f..15f5a07439 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -28,10 +28,15 @@ android { signingConfigs { development { - storeFile file("../keystores/debug.keystore") - storePassword "android" - keyAlias "androiddebugkey" - keyPassword "android" + storeFile file('OsmAndHms.jks') + keyAlias 'OsmAndHms' + keyPassword 'targeting' + storePassword 'targeting' + +// storeFile file("../keystores/debug.keystore") +// storePassword "android" +// keyAlias "androiddebugkey" +// keyPassword "android" } publishing { diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index 7b0351b3a9..1eb62956f1 100644 --- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -226,6 +226,16 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } + @Override + public void manageSubscription(@NonNull Context ctx, @Nullable String sku) { + String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName(); + if (!Algorithms.isEmpty(sku)) { + url += "&sku=" + sku; + } + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + ctx.startActivity(intent); + } + @Nullable private SkuDetails getSkuDetails(@NonNull String sku) { List skuDetailsList = this.skuDetailsList; diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java index 2bfef4b76b..c7e6cd11be 100755 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/CipherUtil.java @@ -37,7 +37,7 @@ import java.security.spec.X509EncodedKeySpec; public class CipherUtil { private static final String TAG = "CipherUtil"; private static final String SIGN_ALGORITHMS = "SHA256WithRSA"; - private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAooen3X9jSWarxugznzzMSvp4zir1Pg6uPOm7fqlLOL0Ix52e5FpeotMx871pQ9hrCkiyFg2e6UxD8IXXjvK6QJQbjNJ2jIfKkCusm90yloSEfvyLeiq5y7zg4+DoPglHi8RxZ9y308YIqnRDoslfGm5DnWa8RKUvFRVRiu1p3FN4SYIa/FWLtS5yygemtqMJi8I14V7xqQ5wExCGeSA6j1/AAWXEwZncJwKn0BTXQSvwVBPBRM5ksgt4q+Sc484ZIbntATyxsUipnEBFxq1OXn5Zw5/vVxUC8RSyDMQ/kC2RaEcFtA1tlIIjIdurbpNg3tyViPfQUQndvOs4nDrFzwIDAQAB"; + private static final String PUBLIC_KEY = "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsB+oH8rYQncwpTqGa0kS/5E725HJrq2sW1ThAZtmorYVi52Yt9PmZvNDz7284ol9C2skrKQR34eIer8Tr7Qqq3mlNo+/LVUpq9sa++kB2glaG6jj5NNjM3w4nVYHFIYkd5AQhodJgmqFvnp2s7r7YmyQVXZSehei5bA1G70Bs+El9cSv9shNNGTCaU3ARUu2hy3Ltkc/ov7/ZYYpiwjbyD3cmoMh9jO1++zztXb2phjv1h9zeJOp1i6HsotZll+c9J4jjV3GhrF+ZJm5WrSzGLDLtwSldRpMZFxrSvAJJstjzhDz3LpUM+nPV3HZ5VQ/xosmwWYmiibo89E1gw8p73NTBXHzuQMJcTJ6vTjD8LeMskpXHZUAGhifmFLGN1LbNP9662ulCV12kIbXuzWCwwi/h0DWqmnjKmLvzc88e4BrGrp2zZUnHz7m15voPG+4cQ3z9+cwS4gEI3SUTiFyQGE539SO/11VkkQAJ8P7du1JFNqQw5ZEW3AoE1iUsp5XAgMBAAE="; /** * the method to check the signature for the data returned from the interface @@ -65,7 +65,7 @@ public class CipherUtil { java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS); signature.initVerify(pubKey); - signature.update(content.getBytes("utf-8")); + signature.update(content.getBytes("UTF-8")); boolean bverify = signature.verify(Base64.decode(sign, Base64.DEFAULT)); return bverify; diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index 61de5125c0..2894309c17 100644 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -8,6 +8,9 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.huawei.hmf.tasks.OnFailureListener; +import com.huawei.hmf.tasks.OnSuccessListener; +import com.huawei.hmf.tasks.Task; import com.huawei.hms.iap.Iap; import com.huawei.hms.iap.IapClient; import com.huawei.hms.iap.entity.InAppPurchaseData; @@ -18,12 +21,15 @@ import com.huawei.hms.iap.entity.ProductInfo; import com.huawei.hms.iap.entity.ProductInfoResult; import com.huawei.hms.iap.entity.PurchaseIntentResult; import com.huawei.hms.iap.entity.PurchaseResultInfo; +import com.huawei.hms.iap.entity.StartIapActivityReq; +import com.huawei.hms.iap.entity.StartIapActivityResult; import net.osmand.AndroidUtils; import net.osmand.plus.OsmandApplication; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo; +import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference; import net.osmand.util.Algorithms; @@ -109,33 +115,37 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { public void run(InAppPurchaseHelper helper) { try { ProductInfo productInfo = getProductInfo(productId); - IapRequestHelper.createPurchaseIntent(getIapClient(), productInfo.getProductId(), - IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { - @Override - public void onSuccess(PurchaseIntentResult result) { - if (result == null) { - logError("result is null"); - } else { - // you should pull up the page to complete the payment process - IapRequestHelper.startResolutionForResult(activity, result.getStatus(), Constants.REQ_CODE_BUY_INAPP); - } - commandDone(); - } - - @Override - public void onFail(Exception e) { - int errorCode = ExceptionHandle.handle(activity, e); - if (errorCode != ExceptionHandle.SOLVED) { - logDebug("createPurchaseIntent, returnCode: " + errorCode); - if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) { - logError("already own this product"); + if (productInfo != null) { + IapRequestHelper.createPurchaseIntent(getIapClient(), productInfo.getProductId(), + IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { + @Override + public void onSuccess(PurchaseIntentResult result) { + if (result == null) { + logError("result is null"); } else { - logError("unknown error"); + // you should pull up the page to complete the payment process + IapRequestHelper.startResolutionForResult(activity, result.getStatus(), Constants.REQ_CODE_BUY_INAPP); } + commandDone(); } - commandDone(); - } - }); + + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle(activity, e); + if (errorCode != ExceptionHandle.SOLVED) { + logDebug("createPurchaseIntent, returnCode: " + errorCode); + if (OrderStatusCode.ORDER_PRODUCT_OWNED == errorCode) { + logError("already own this product"); + } else { + logError("unknown error"); + } + } + commandDone(); + } + }); + } else { + commandDone(); + } } catch (Exception e) { complain("Cannot launch full version purchase!"); logError("purchaseFullVersion Error", e); @@ -148,13 +158,42 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { @Override public void purchaseFullVersion(@NonNull final Activity activity) throws UnsupportedOperationException { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); - exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, getPurchaseInAppCommand(activity, "")); + exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, getPurchaseInAppCommand(activity, purchases.getFullVersion().getSku())); } @Override public void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); - exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, getPurchaseInAppCommand(activity, "")); + exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, getPurchaseInAppCommand(activity, purchases.getDepthContours().getSku())); + } + + @Override + public void manageSubscription(@NonNull Context ctx, @Nullable String sku) { + if (uiActivity != null) { + StartIapActivityReq req = new StartIapActivityReq(); + if (!Algorithms.isEmpty(sku)) { + req.setSubscribeProductId(sku); + req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_EDIT_ACTIVITY); + } else { + req.setType(StartIapActivityReq.TYPE_SUBSCRIBE_MANAGER_ACTIVITY); + } + Task task = getIapClient().startIapActivity(req); + task.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StartIapActivityResult result) { + logDebug("startIapActivity: onSuccess"); + Activity activity = (Activity) uiActivity; + if (result != null && AndroidUtils.isActivityNotDestroyed(activity)) { + result.startActivity(activity); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception e) { + logDebug("startIapActivity: onFailure"); + } + }); + } } @Nullable @@ -285,16 +324,18 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { protected InAppCommand getRequestInventoryCommand() { return new InAppCommand() { + @Override + protected void commandDone() { + super.commandDone(); + inventoryRequested = false; + } + @Override public void run(InAppPurchaseHelper helper) { logDebug("Setup successful. Querying inventory."); try { productInfos = new ArrayList<>(); - if (uiActivity != null) { - obtainOwnedSubscriptions(); - } else { - commandDone(); - } + obtainOwnedSubscriptions(); } catch (Exception e) { logError("queryInventoryAsync Error", e); notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); @@ -326,92 +367,95 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } private void obtainOwnedInApps(final String continuationToken) { - // Query users' purchased non-consumable products. - IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_NONCONSUMABLE, - continuationToken, new IapApiCallback() { - @Override - public void onSuccess(OwnedPurchasesResult result) { - ownedInApps.add(result); - if (result != null && !TextUtils.isEmpty(result.getContinuationToken())) { - obtainOwnedInApps(result.getContinuationToken()); - } else { - obtainSubscriptionsInfo(); + if (uiActivity != null) { + // Query users' purchased non-consumable products. + IapRequestHelper.obtainOwnedPurchases(getIapClient(), IapClient.PriceType.IN_APP_NONCONSUMABLE, + continuationToken, new IapApiCallback() { + @Override + public void onSuccess(OwnedPurchasesResult result) { + ownedInApps.add(result); + if (result != null && !TextUtils.isEmpty(result.getContinuationToken())) { + obtainOwnedInApps(result.getContinuationToken()); + } else { + obtainSubscriptionsInfo(); + } } - } - - @Override - public void onFail(Exception e) { - logError("obtainOwnedInApps exception", e); - ExceptionHandle.handle((Activity) uiActivity, e); - commandDone(); - } - }); + @Override + public void onFail(Exception e) { + logError("obtainOwnedInApps exception", e); + ExceptionHandle.handle((Activity) uiActivity, e); + commandDone(); + } + }); + } else { + commandDone(); + } } private void obtainSubscriptionsInfo() { - Set productIds = new HashSet<>(); - List subscriptions = purchases.getLiveUpdates().getAllSubscriptions(); - for (InAppSubscription s : subscriptions) { - productIds.add(s.getSku()); - } - productIds.addAll(ownedSubscriptions.getItemList()); - IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), - IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback() { - @Override - public void onSuccess(final ProductInfoResult result) { - if (result == null) { - logError("obtainSubscriptionsInfo: ProductInfoResult is null"); - commandDone(); - return; + if (uiActivity != null) { + Set productIds = new HashSet<>(); + List subscriptions = purchases.getLiveUpdates().getAllSubscriptions(); + for (InAppSubscription s : subscriptions) { + productIds.add(s.getSku()); + } + productIds.addAll(ownedSubscriptions.getItemList()); + IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), + IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback() { + @Override + public void onSuccess(final ProductInfoResult result) { + if (result != null && result.getProductInfoList() != null) { + productInfos.addAll(result.getProductInfoList()); + } + obtainInAppsInfo(); } - productInfos.addAll(result.getProductInfoList()); - obtainInAppsInfo(); - } - @Override - public void onFail(Exception e) { - int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); - if (ExceptionHandle.SOLVED != errorCode) { - LOG.error("Unknown error"); + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); + if (ExceptionHandle.SOLVED != errorCode) { + LOG.error("Unknown error"); + } + commandDone(); } - commandDone(); - } - }); + }); + } else { + commandDone(); + } } private void obtainInAppsInfo() { - Set productIds = new HashSet<>(); - for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { - productIds.add(purchase.getSku()); - } - for (OwnedPurchasesResult result : ownedInApps) { - productIds.addAll(result.getItemList()); - } - IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), - IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { - @Override - public void onSuccess(ProductInfoResult result) { - if (result == null || result.getProductInfoList() == null) { - logError("obtainInAppsInfo: ProductInfoResult is null"); + if (uiActivity != null) { + Set productIds = new HashSet<>(); + for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + productIds.add(purchase.getSku()); + } + for (OwnedPurchasesResult result : ownedInApps) { + productIds.addAll(result.getItemList()); + } + IapRequestHelper.obtainProductInfo(getIapClient(), new ArrayList<>(productIds), + IapClient.PriceType.IN_APP_NONCONSUMABLE, new IapApiCallback() { + @Override + public void onSuccess(ProductInfoResult result) { + if (result != null && result.getProductInfoList() != null) { + productInfos.addAll(result.getProductInfoList()); + } + processInventory(); + } + + @Override + public void onFail(Exception e) { + int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); + if (ExceptionHandle.SOLVED != errorCode) { + LOG.error("Unknown error"); + } commandDone(); - return; } - productInfos.addAll(result.getProductInfoList()); - - processInventory(); - commandDone(); - } - - @Override - public void onFail(Exception e) { - int errorCode = ExceptionHandle.handle((Activity) uiActivity, e); - if (ExceptionHandle.SOLVED != errorCode) { - LOG.error("Unknown error"); - } - commandDone(); - } - }); + }); + } else { + commandDone(); + } } private void processInventory() { @@ -579,17 +623,31 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { if (resultCode == Activity.RESULT_OK) { PurchaseResultInfo result = SubscriptionUtils.getPurchaseResult(activity, data); if (result != null) { - if (OrderStatusCode.ORDER_STATE_SUCCESS == result.getReturnCode()) { - InAppPurchaseData purchaseData = SubscriptionUtils.getInAppPurchaseData(null, - result.getInAppPurchaseData(), result.getInAppDataSignature()); - if (purchaseData != null) { - onPurchaseFinished(purchaseData); - succeed = true; - } else { + switch (result.getReturnCode()) { + case OrderStatusCode.ORDER_STATE_CANCEL: + logDebug("Purchase cancelled"); + break; + case OrderStatusCode.ORDER_STATE_FAILED: + inventoryRequestPending = true; logDebug("Purchase failed"); - } - } else if (OrderStatusCode.ORDER_STATE_CANCEL == result.getReturnCode()) { - logDebug("Purchase cancelled"); + break; + case OrderStatusCode.ORDER_PRODUCT_OWNED: + inventoryRequestPending = true; + logDebug("Product already owned"); + break; + case OrderStatusCode.ORDER_STATE_SUCCESS: + inventoryRequestPending = true; + InAppPurchaseData purchaseData = SubscriptionUtils.getInAppPurchaseData(null, + result.getInAppPurchaseData(), result.getInAppDataSignature()); + if (purchaseData != null) { + onPurchaseFinished(purchaseData); + succeed = true; + } else { + logDebug("Purchase failed"); + } + break; + default: + break; } } else { logDebug("Purchase failed"); @@ -611,7 +669,12 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { case OrderStatusCode.ORDER_STATE_CANCEL: logDebug("Order has been canceled"); break; + case OrderStatusCode.ORDER_STATE_FAILED: + inventoryRequestPending = true; + logDebug("Order has been failed"); + break; case OrderStatusCode.ORDER_PRODUCT_OWNED: + inventoryRequestPending = true; logDebug("Product already owned"); break; case OrderStatusCode.ORDER_STATE_SUCCESS: diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java index 49ff72cd3b..4ed0021b6f 100644 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchasesImpl.java @@ -5,6 +5,8 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.huawei.hms.iap.entity.ProductInfo; + import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -26,8 +28,18 @@ public class InAppPurchasesImpl extends InAppPurchases { fullVersion = FULL_VERSION; depthContours = DEPTH_CONTOURS_FREE; contourLines = CONTOUR_LINES_FREE; - liveUpdates = new LiveUpdatesInAppPurchasesFree(); inAppPurchases = new InAppPurchase[] { fullVersion, depthContours, contourLines }; + + liveUpdates = new LiveUpdatesInAppPurchasesFree(); + for (InAppSubscription s : liveUpdates.getAllSubscriptions()) { + if (s instanceof InAppPurchaseLiveUpdatesMonthly) { + if (s.isDiscounted()) { + discountedMonthlyLiveUpdates = s; + } else { + monthlyLiveUpdates = s; + } + } + } } @Override @@ -57,7 +69,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseFullVersion extends InAppPurchase { - private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; + private static final String SKU_FULL_VERSION_PRICE = "net.osmand.huawei.full"; InAppPurchaseFullVersion() { super(SKU_FULL_VERSION_PRICE); @@ -71,7 +83,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseDepthContoursFree extends InAppPurchaseDepthContours { - private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.seadepth"; + private static final String SKU_DEPTH_CONTOURS_FREE = "net.osmand.huawei.seadepth"; InAppPurchaseDepthContoursFree() { super(SKU_DEPTH_CONTOURS_FREE); @@ -80,7 +92,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseContourLinesFree extends InAppPurchaseContourLines { - private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.contourlines"; + private static final String SKU_CONTOUR_LINES_FREE = "net.osmand.huawei.contourlines"; InAppPurchaseContourLinesFree() { super(SKU_CONTOUR_LINES_FREE); @@ -89,7 +101,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseLiveUpdatesMonthlyFree extends InAppPurchaseLiveUpdatesMonthly { - private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.test.monthly"; + private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.huawei.monthly"; InAppPurchaseLiveUpdatesMonthlyFree() { super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1); @@ -108,7 +120,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseLiveUpdates3MonthsFree extends InAppPurchaseLiveUpdates3Months { - private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.test.3months"; + private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.huawei.3months"; InAppPurchaseLiveUpdates3MonthsFree() { super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1); @@ -127,7 +139,7 @@ public class InAppPurchasesImpl extends InAppPurchases { private static class InAppPurchaseLiveUpdatesAnnualFree extends InAppPurchaseLiveUpdatesAnnual { - private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.test.annual"; + private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.huawei.annual"; InAppPurchaseLiveUpdatesAnnualFree() { super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1); diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java index 1dffffc252..3b249cb300 100755 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/SubscriptionUtils.java @@ -120,9 +120,8 @@ public class SubscriptionUtils { } } else { Log.e(TAG, "check the data signature fail"); + return getFailedPurchaseResultInfo(); } - return getFailedPurchaseResultInfo(); - default: Log.e(TAG, "returnCode: " + returnCode + " , errMsg: " + errMsg); break; diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java index 1e3b650a8f..c909d50845 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java @@ -428,7 +428,7 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment buttonCancelView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - manageSubscription(ctx, s.getSku()); + purchaseHelper.manageSubscription(ctx, s.getSku()); } }); div.setVisibility(View.VISIBLE); @@ -538,15 +538,6 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment } } - private void manageSubscription(@NonNull Context ctx, @Nullable String sku) { - String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName(); - if (!Algorithms.isEmpty(sku)) { - url += "&sku=" + sku; - } - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(intent); - } - private ViewGroup buildPlanTypeCard(@NonNull Context ctx, ViewGroup container) { if (getPlanTypeFeatures().length == 0) { return null; diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index ff734e6af5..e99bed63b7 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -2,6 +2,7 @@ package net.osmand.plus.inapp; import android.annotation.SuppressLint; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.text.TextUtils; @@ -265,6 +266,8 @@ public abstract class InAppPurchaseHelper { public abstract void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException; + public abstract void manageSubscription(@NonNull Context ctx, @Nullable String sku); + @SuppressLint("StaticFieldLeak") private class LiveUpdatesPurchaseTask extends AsyncTask { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java index 5ccb49a70e..5004e97165 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java @@ -73,7 +73,7 @@ public abstract class InAppPurchases { public InAppSubscription getPurchasedMonthlyLiveUpdates() { if (monthlyLiveUpdates.isAnyPurchased()) { return monthlyLiveUpdates; - } else if (discountedMonthlyLiveUpdates.isAnyPurchased()) { + } else if (discountedMonthlyLiveUpdates != null && discountedMonthlyLiveUpdates.isAnyPurchased()) { return discountedMonthlyLiveUpdates; } return null; From 82b4009fe314548cc27048d7483bec5d14361897 Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sun, 4 Oct 2020 06:24:41 +0000 Subject: [PATCH 65/75] Translated using Weblate (Russian) Currently translated at 99.9% (3486 of 3488 strings) --- OsmAnd/res/values-ru/strings.xml | 74 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index cdc7518bfc..5a8d26fe8c 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -86,7 +86,7 @@ Видимые Восстановить покупки Шрифты карты - Посмотреть на карте + Анализ на карте Морские карты Контуры морских глубин Контуры морских глубин @@ -115,7 +115,7 @@ Сбалансированный Предпочитать переулки Выберите предпочтительный рельеф. - Карта уклонов + Уклон Добавить новую папку Точки удалены. Вы уверены, что хотите удалить %1$d точки\? @@ -317,15 +317,15 @@ Открыть внешний проигрыватель Удалить эту запись? недоступно - Аудиозаметка + Запись аудио Запись видео - Слой аудиозаписей - Запись не может быть воспроизведена. + Слой медиазаписей + Не удаётся воспроизвести запись. Удалить запись Проиграть Запись %1$s %3$s %2$s Запись - Аудиозаметки + Медиазаметки OsmAnd-плагин для линий высот Измерение расстояний Нажмите «Использовать местоположение…» для добавления заметки к месту. @@ -521,11 +521,11 @@ Для этого региона есть локальные векторные карты. \n\t \n\tДля использования выберите их в качестве источника (Меню → Настройка карты → Источник карты → Локальные векторные карты). - Голосовые инструкции + Аудиоканал голосовых инструкций Выберите канал вывода голосовых подсказок. - Канал голосовых звонков (прерывает автомобильную Bluetooth стереосистему) - Канал уведомлений - Канал медиа/навигации + Голосовые звонки (для прерывания автомобильной стереосистемы Bluetooth) + Уведомления + Мультимедиа, навигация Приложение не может загрузить слой карты %1$s, переустановка может решить проблему. Отрегулируйте прозрачность наложения. Прозрачность наложения @@ -669,9 +669,9 @@ Локальные векторные карты Редактировать POI Удалить POI - По направлению компаса - По направлению движения - Не вращать (север сверху) + по компасу + по направлению движения + не вращать (север сверху) Выравнивание карты: Ориентация карты Детали маршрута @@ -1258,7 +1258,7 @@ Отменить маршрут Очистить пункт назначения Искать улицу в ближайших населённых пунктах - В порядке следования домов + Расположить в оптимальном порядке Доступно %1$d файлов для скачивания осталось %1$d файлов Подождите, пока завершится текущая операция @@ -1358,7 +1358,7 @@ Избегать грунтовых дорог Без паромов Исключить паромные переправы - Максимальная масса + Предельная масса Укажите допустимый предел массы автомобиля для учёта при построении маршрута. Отображение карты Навигационные знаки (водоёмы) @@ -1446,14 +1446,14 @@ Разное Локализация Голосовые подсказки приостанавливают воспроизведение музыки. - Приостановить музыку + Приостанавливать музыку Поделиться маршрутом используя файл GPX Неправильный формат: %s Маршрут предоставленный через OsmAnd - Только вручную (нажатием «стрелочки») + При нажатии на стрелку (вручную) Повторять навигационные инструкции с регулярными интервалами. - Повторять навигационные инструкции - Объявление прибытия + Повтор инструкций при навигации + Объявление о прибытии Как скоро следует сообщать о прибытии? Места, отправленные в OsmAnd Рассчитать маршрут между точками @@ -1540,10 +1540,10 @@ Продолжить навигацию Приостановить навигацию Визуализация пути по шкале SAC. - Визуализация пути согласно трассам OSMC. - Пораньше - Как обычно - Попозже + Отрисовка дорог согласно трассам OSMC. + Раннее + По умолчанию + Позднее На последних метрах Пеший горный туризм по шкале (SAC) Наложение туристических меток @@ -1816,7 +1816,7 @@ Напечатайте для поиска Номера домов Избегать перехода границы - Максимальная высота + Предельная высота Укажите высоту транспортного средства для учёта при построении маршрута. Умный пересчёт маршрута Для больших маршрутов пересчитывать только начало. @@ -2065,7 +2065,7 @@ Разбиение на клипы Использовать разбиение на клипы Циклическая перезапись клипов при превышении заданного объёма хранилища. - Поменять местами пункты отправления и назначения + Поменять местами пункты отправления и назначения Удалить Подземные объекты Данные недоступны @@ -2380,7 +2380,7 @@ Избегать ледовых дорог и бродов. Моё местоположение Финиш - Сортировать + Сортировка Экспорт маркеров в следующий файл GPX: Маркеры Изменить заметку @@ -2392,7 +2392,7 @@ Критерий сортировки: Выберите способ указания расстояния и направления до маркеров на карте: Смена ориентации карты - Выберите скорость, при которой переключается ориентация карты с «По направлению движения» на «По направлению компаса». + Выберите скорость, при которой ориентация по направлению движения переключится на ориентацию по компасу. Все маркеры перемещены в историю Маркер перемещён в историю Маркер перемещён в действующие @@ -2683,7 +2683,7 @@ OSM-заметки Впереди туннель Туннели - Сделать отправной точкой + Сделать пунктом отправления Введите имя файла. Ошибка импорта карты Карта импортирована @@ -2788,7 +2788,7 @@ Для катания на лыжах. Выделяет горнолыжные трассы, подъёмники, трассы для беговых лыж и прочее. Меньше отвлекающих второстепенных объектов на карте. Скачать все Простой стиль для вождения. Мягкий ночной режим, контурные линии, контрастные дороги в оранжевом стиле, тусклые второстепенные объекты карты. - Для пеших походов, трекинга и велосипедных прогулок на природе. Читабельный на открытом воздухе и при сложном освещении. Контрастные дороги и природные объекты, различные типы маршрутов, контурные линии с расширенными настройками, дополнительные детали. Функция «Качество дорожного покрытия» позволяет различать дороги с различным качеством поверхности. Нет ночного режима. + Для пеших походов, трекинга и велопоездок на природе. Хорошо читается при сложном освещении. Контрастные дороги и природные объекты, различные типы маршрутов, контурные линии с расширенными настройками, дополнительные детали. Параметр «Дорожное покрытие» позволяет различать поверхность и качество дорог. Ночной режим отсутствует. Старый стиль по умолчанию «Mapnik». Похожие цвета на «Mapnik». Стиль общего назначения. Густонаселённые города показаны упрощённо. Выделяет контурные линии, маршруты, качество поверхности, ограничения доступа, дорожные щиты, визуализация пешеходных маршрутов по шкале SAC, объекты спортивных сплавов. Открыть ссылку Википедии в онлайн @@ -2796,7 +2796,7 @@ Получите подписку на OsmAnd Live, чтобы читать статьи в Википедии и Викигиде в автономном режиме. Как открыть ссылку? Читать Википедию в автономном режиме - Туристический стиль с высоким контрастом и максимальной детализацией. Включает все функции стиля OsmAnd по умолчанию, также отображая как можно больше деталей, в частности дороги, тропы и другие пути для передвижения. Чёткое различие между типами дорог, как во многих туристических атласах. Подходит для дневного, ночного и уличного использования. + Туристический стиль с высоким контрастом и максимальной детализацией. Включает все функции стиля OsmAnd по умолчанию, отображая максимальное количество деталей, в частности дороги, тропы и другие пути передвижения. Чёткое различие между типами дорог (как в туристических атласах). Подходит для использования днём, ночью и при ярком освещении. Сохранить Для езды по бездорожью, основано на топографическом стиле (англ. «Topo»), можно использовать с зелёными спутниковыми снимками в качестве подложки. Уменьшенная толщина основных дорог, увеличенная толщина путей, дорожек, велосипедных и других маршрутов. Модификация стиля по умолчанию для увеличения контраста пешеходных и велосипедных дорог. Использует старые цвета Mapnik. @@ -2987,7 +2987,7 @@ Ступеньки Тропа Велодорожка - Неопределённая + Не определено Узнайте больше о маршрутизации OsmAnd в нашем блоге. Навигация на общественном транспорте в настоящее время проходит бета-тестирование, возможны ошибки и неточности. Добавить промежуточную точку @@ -3101,7 +3101,7 @@ Использовать WunderLINQ для контроля Значок Собранные данные - Нажмите ещё раз, чтобы изменить ориентацию карты + Нажмите дважды для смены ориентации карты Последний запуск OsmAnd завершился ошибкой. Пожалуйста, помогите нам улучшить OsmAnd, отправив нам отчёт об ошибке. Режим: %s Режим пользователя, полученный из: %s @@ -3233,7 +3233,7 @@ Карта во время навигации Скорость движения, размеры, масса транспортного средства Параметры транспортного средства - Голосовые инструкции работают только при навигации. + Голосовые инструкции работают только во время навигации. Навигационные инструкции и объявления Голосовые подсказки Экранные оповещения @@ -3390,7 +3390,7 @@ Эксперт Фрирайд Экстрим - Неопределённо + Неопределённая Канатная дорога Соединение Симулировать свою позицию используя записанный GPX трек. @@ -3555,7 +3555,7 @@ Невозможно разобрать геоссылку «%s». Для отображения затенения рельефа требуются дополнительные карты. Мин. - Отображение затенения рельефа или карты уклонов. Подробнее об этих типах карт вы можете прочитать на нашем сайте. + Способы отображения рельефа местности: посредством теней (затенение рельефа) или цветов (карта уклонов). Подробнее об этих типах карт вы можете прочитать на нашем сайте. Прозрачность Уровни масштаба Пересчитывать маршрут в случае отклонения @@ -3587,7 +3587,7 @@ Затенение рельефа Для отображения склонов, вершин и низменностей используются тёмные тени. Для отображения уклонов требуются дополнительные карты. - Уклоны + Карта уклонов Заменить этой точкой другую. Изменения применены к профилю «%1$s». Невозможно прочитать из «%1$s». @@ -3885,7 +3885,7 @@ ПОВТОРИТЬ • Обновлённая функция планирования маршрута позволяет применять к сегментам разные режимы навигации и настраивать привязку к дорогам \n -\n • Новые настройки вида треков: выбор цвета и толщины линии, стрелки направления, метки начала и конца маршрута +\n • Новые настройки вида треков: выбор цвета и толщины линии, указатели направления, метки начала и конца маршрута \n \n • Повышенная видимость велосипедных узлов \n From 95ba1e91edc5b1d81292a56c77bb21c513f808e9 Mon Sep 17 00:00:00 2001 From: Dmitriy Prodchenko Date: Sat, 3 Oct 2020 16:59:10 +0000 Subject: [PATCH 66/75] Translated using Weblate (Russian) Currently translated at 99.9% (3486 of 3488 strings) --- OsmAnd/res/values-ru/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 5a8d26fe8c..f387316703 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -126,7 +126,7 @@ Средняя скорость Время в движении Общее время - Макс. + Максимальная Отправление Прибытие Цвет @@ -901,7 +901,7 @@ Дом Пересечение улиц Обновить карту - POI + Добавление POI Да Отмена Нет @@ -1178,7 +1178,7 @@ Достопримечательности Последний промежуточный пункт Первый промежуточный пункт - Последний промежуточный пункт + Добавить последним промежуточным пунктом Первый промежуточный пункт Заменить пункт назначения Вы уже задали пункт назначения @@ -1386,7 +1386,7 @@ Задать пункт назначения Предпочтения маршрута Информация про маршрут - Добавить пункт назначения + Добавить новым пунктом назначения Использовать показанный путь для навигации? Рассчитать сегмент маршрута OsmAnd без интернета Рассчитать маршрут OsmAnd для первого и последнего сегмента маршрута @@ -1667,7 +1667,7 @@ Моё местоположение Статус GPS Точки - Парковка + Место для парковки УДАЛИТЬ ТЕГ Редактировать группу Вам необходимо подключение к сети для установки этого плагина. @@ -2134,7 +2134,7 @@ Переместить ↑ Переместить ↓ Завершить навигацию - Избегать + Избегать дорог Публичное имя Поддерживаемый регион Введите публичное имя @@ -2917,7 +2917,7 @@ Предыдущий маршрут Сначала задайте пункт назначения Поменять - Ещё + Показать больше Отображаемые треки Показать/скрыть треки Скрыть треки From 5b6f2692a352c3ac52fba05cc76386c895b8c20b Mon Sep 17 00:00:00 2001 From: Dmitriy Prodchenko Date: Sun, 4 Oct 2020 06:40:43 +0000 Subject: [PATCH 67/75] Translated using Weblate (Russian) Currently translated at 99.9% (3486 of 3488 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 f387316703..299cf1df71 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -669,7 +669,7 @@ Локальные векторные карты Редактировать POI Удалить POI - по компасу + по направлению компаса по направлению движения не вращать (север сверху) Выравнивание карты: From dbf68d2872d0a089b8206ace392a92a3b97d9d21 Mon Sep 17 00:00:00 2001 From: ace shadow Date: Sat, 3 Oct 2020 20:19:33 +0000 Subject: [PATCH 68/75] Translated using Weblate (Slovak) Currently translated at 94.9% (3633 of 3825 strings) --- OsmAnd/res/values-sk/phrases.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/OsmAnd/res/values-sk/phrases.xml b/OsmAnd/res/values-sk/phrases.xml index fc275bf7c3..28ac2c4297 100644 --- a/OsmAnd/res/values-sk/phrases.xml +++ b/OsmAnd/res/values-sk/phrases.xml @@ -3628,4 +3628,28 @@ Malé elektrické spotrebiče Tabuľa odjazdov/cestovný poriadok Doplnenie pitnej vody + Nasávanie + Pod tlakom + Spodná voda + Potrubie + Sieť doplnenia pitnej vody + Doplnenie pitnej vody: nie + Áno + Prekážka + Výška vody: pod strednou hladinou + Výška vody: nad strednou hladinou + Výška vody: prekrýva + Výška vody: suché + Výška vody: ponorené + Výška vody: čiastočne ponorené + Nesprávne + Primitívne + Kontrastné + Len keď je povolená chôdza + Nie + Áno + Typ búdky + Búdka + Nie + Áno \ No newline at end of file From 1b2e9c3061b0021b4b5ef919cb99a91c0060c95c Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sun, 4 Oct 2020 05:34:58 +0000 Subject: [PATCH 69/75] Translated using Weblate (Russian) Currently translated at 100.0% (267 of 267 strings) Translation: OsmAnd/Telegram Translate-URL: https://hosted.weblate.org/projects/osmand/telegram/ru/ --- OsmAnd-telegram/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd-telegram/res/values-ru/strings.xml b/OsmAnd-telegram/res/values-ru/strings.xml index daff0e950b..a494bf809b 100644 --- a/OsmAnd-telegram/res/values-ru/strings.xml +++ b/OsmAnd-telegram/res/values-ru/strings.xml @@ -75,7 +75,7 @@ По расстоянию По имени По группе - Сортировать + Сортировка Сортировать по Отстановить все Выход From 2b72d19048e6d31a511b6cba252de664c96bc62f Mon Sep 17 00:00:00 2001 From: Deelite <556xxy@gmail.com> Date: Sun, 4 Oct 2020 10:29:12 +0000 Subject: [PATCH 70/75] Translated using Weblate (Russian) Currently translated at 99.9% (3486 of 3488 strings) --- OsmAnd/res/values-ru/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 299cf1df71..8b167f73b2 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -844,7 +844,7 @@ Сохранить текущий трек Укажите интервал фиксирования точек для записи трека во время навигации Интервал записи во время навигации - Во время навигации GPX треки будут автоматически сохранены в папку с треками. + Во время навигации GPX-треки будут автоматически сохранены в папку с треками. Автозапись трека во время навигации Обновить карту Обновить часть карты @@ -1160,7 +1160,7 @@ \n — подсказки полосы движения, отображение ограничения скорости, предварительно записанные и синтезированные голосовые подсказки \n Без автомагистралей - Привязываться к дорогам во время навигации. + Привязывать позицию к дороге во время навигации. Привязка к дороге Промежуточный пункт %1$s слишком далеко от ближайшей дороги. Вы прибыли в промежуточный пункт @@ -1445,8 +1445,8 @@ Голос Разное Локализация - Голосовые подсказки приостанавливают воспроизведение музыки. - Приостанавливать музыку + Приостановка воспроизведения во время подсказок. + Прерывать музыку Поделиться маршрутом используя файл GPX Неправильный формат: %s Маршрут предоставленный через OsmAnd @@ -1492,7 +1492,7 @@ %1$s точек Точка %1$s %1$s \nМаршрутных точек %2$s - Показывать кнопки изменения масштаба во время навигации. + Показывать кнопки масштаба во время навигации. Кнопки масштаба Сортировать по расстоянию Сортировать по имени @@ -2801,7 +2801,7 @@ Для езды по бездорожью, основано на топографическом стиле (англ. «Topo»), можно использовать с зелёными спутниковыми снимками в качестве подложки. Уменьшенная толщина основных дорог, увеличенная толщина путей, дорожек, велосипедных и других маршрутов. Модификация стиля по умолчанию для увеличения контраста пешеходных и велосипедных дорог. Использует старые цвета Mapnik. Получите OsmAnd Live, чтобы разблокировать все функции: ежедневные обновления карт с неограниченной загрузкой, все платные и бесплатные плагины, Википедия, Викигид и многое другое. - Промежуточное время прибытия + Прибытие в промежуточный пункт Прибытие в промежуточный пункт Редактировать действие Пожалуйста, пришлите скриншот этого уведомления на support@osmand.net @@ -2891,7 +2891,7 @@ Точки интереса (POI) Расчёт маршрута… Общественный транспорт - Выберите дорогу на карте или из списка ниже, которую вы хотите избежать во время навигации: + Выберите на карте или в списке ниже дорогу, которой хотите избежать при навигации: Моделировать навигацию Выберите файл трека для следования Голосовые подсказки @@ -3237,7 +3237,7 @@ Навигационные инструкции и объявления Голосовые подсказки Экранные оповещения - Настройка параметров маршрута + Настройки маршрутизации Параметры маршрута Буфер Logcat Настройки плагинов From 197d30b584e3e5e678e551acea474ad6ccc2dbd5 Mon Sep 17 00:00:00 2001 From: Dmitriy Prodchenko Date: Sun, 4 Oct 2020 06:47:54 +0000 Subject: [PATCH 71/75] Translated using Weblate (Russian) Currently translated at 99.9% (3486 of 3488 strings) --- OsmAnd/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index 8b167f73b2..9992fd35fb 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -1452,7 +1452,7 @@ Маршрут предоставленный через OsmAnd При нажатии на стрелку (вручную) Повторять навигационные инструкции с регулярными интервалами. - Повтор инструкций при навигации + Повторять навигационные инструкции Объявление о прибытии Как скоро следует сообщать о прибытии? Места, отправленные в OsmAnd @@ -3101,7 +3101,7 @@ Использовать WunderLINQ для контроля Значок Собранные данные - Нажмите дважды для смены ориентации карты + Нажмите ещё раз для смены ориентации карты Последний запуск OsmAnd завершился ошибкой. Пожалуйста, помогите нам улучшить OsmAnd, отправив нам отчёт об ошибке. Режим: %s Режим пользователя, полученный из: %s From 1acf5f41c748397e6f5fc0ddadffe1ac99f82723 Mon Sep 17 00:00:00 2001 From: max-klaus Date: Sun, 4 Oct 2020 16:00:07 +0300 Subject: [PATCH 72/75] Finished huawei integration --- OsmAnd/build.gradle | 17 ++++------ OsmAnd/res/values/strings.xml | 1 + .../plus/inapp/InAppPurchaseHelperImpl.java | 20 ++++++++++++ .../plus/inapp/InAppPurchaseHelperImpl.java | 22 +++++++------ .../plus/activities/MapActivityActions.java | 2 +- .../OsmandInAppPurchaseActivity.java | 32 +++++++++++++------ ...ChoosePlanHillshadeSrtmDialogFragment.java | 2 +- .../plus/inapp/InAppPurchaseHelper.java | 22 +++++++++++-- .../plus/settings/backend/OsmandSettings.java | 1 + .../osmand/plus/srtmplugin/SRTMPlugin.java | 4 ++- 10 files changed, 88 insertions(+), 35 deletions(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 15f5a07439..a87a05b117 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -28,15 +28,10 @@ android { signingConfigs { development { - storeFile file('OsmAndHms.jks') - keyAlias 'OsmAndHms' - keyPassword 'targeting' - storePassword 'targeting' - -// storeFile file("../keystores/debug.keystore") -// storePassword "android" -// keyAlias "androiddebugkey" -// keyPassword "android" + storeFile file("../keystores/debug.keystore") + storePassword "android" + keyAlias "androiddebugkey" + keyPassword "android" } publishing { @@ -49,13 +44,13 @@ android { publishingHuawei { storeFile file("/var/lib/jenkins/osmand_hw_key") storePassword System.getenv("OSMAND_HW_APK_PASSWORD") - keyAlias "OsmAndHms" + keyAlias "osmand" keyPassword System.getenv("OSMAND_HW_APK_PASSWORD") } } defaultConfig { - minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 17 + minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 targetSdkVersion 28 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 14e08c0176..b110f8df1e 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,7 @@ Thx - Hardy --> + Thank you for purchasing \'Contour lines\' Name: A – Z Name: Z – A Last modified diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index 1eb62956f1..c6925a5e63 100644 --- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -1,6 +1,10 @@ package net.osmand.plus.inapp; import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -13,11 +17,14 @@ import com.android.billingclient.api.SkuDetailsResponseListener; import net.osmand.AndroidUtils; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.util.BillingManager; import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.util.Algorithms; import java.lang.ref.WeakReference; @@ -174,6 +181,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } + @Override public void purchaseFullVersion(@NonNull final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppCommand() { @@ -200,6 +208,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } + @Override public void purchaseDepthContours(@NonNull final Activity activity) { notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppCommand() { @@ -226,6 +235,17 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { }); } + @Override + public void purchaseContourLines(@NonNull Activity activity) throws UnsupportedOperationException { + OsmandPlugin plugin = OsmandPlugin.getPlugin(SRTMPlugin.class); + if(plugin == null || plugin.getInstallURL() == null) { + Toast.makeText(activity.getApplicationContext(), + activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show(); + } else { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); + } + } + @Override public void manageSubscription(@NonNull Context ctx, @Nullable String sku) { String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName(); diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java index 2894309c17..c3d6fc193c 100644 --- a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -167,6 +167,12 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, getPurchaseInAppCommand(activity, purchases.getDepthContours().getSku())); } + @Override + public void purchaseContourLines(@NonNull Activity activity) throws UnsupportedOperationException { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES); + exec(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES, getPurchaseInAppCommand(activity, purchases.getContourLines().getSku())); + } + @Override public void manageSubscription(@NonNull Context ctx, @Nullable String sku) { if (uiActivity != null) { @@ -255,7 +261,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } if (inAppPurchase instanceof InAppSubscription) { String introductoryPrice = productInfo.getSubSpecialPrice(); - String introductoryPricePeriod = productInfo.getSubSpecialPeriod(); + String introductoryPricePeriod = productInfo.getSubPeriod(); int introductoryPriceCycles = productInfo.getSubSpecialPeriodCycles(); long introductoryPriceAmountMicros = productInfo.getSubSpecialPriceMicros(); if (!Algorithms.isEmpty(introductoryPrice)) { @@ -497,7 +503,6 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { fetchInAppPurchase(fullVersion, fullPriceDetails, purchaseData); } } - InAppPurchase depthContours = getDepthContours(); if (hasDetails(depthContours.getSku())) { InAppPurchaseData purchaseData = getPurchaseData(depthContours.getSku()); @@ -506,7 +511,6 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { fetchInAppPurchase(depthContours, depthContoursDetails, purchaseData); } } - InAppPurchase contourLines = getContourLines(); if (hasDetails(contourLines.getSku())) { InAppPurchaseData purchaseData = getPurchaseData(contourLines.getSku()); @@ -516,17 +520,15 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { } } - InAppPurchaseData fullVersionPurchase = getPurchaseData(fullVersion.getSku()); - boolean fullVersionPurchased = fullVersionPurchase != null; - if (fullVersionPurchased) { + if (getPurchaseData(fullVersion.getSku()) != null) { ctx.getSettings().FULL_VERSION_PURCHASED.set(true); } - - InAppPurchaseData depthContoursPurchase = getPurchaseData(depthContours.getSku()); - boolean depthContoursPurchased = depthContoursPurchase != null; - if (depthContoursPurchased) { + if (getPurchaseData(depthContours.getSku()) != null) { ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); } + if (getPurchaseData(contourLines.getSku()) != null) { + ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true); + } // Do we have the live updates? boolean subscribedToLiveUpdates = false; diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java index dbd0f133fa..1d6e2c2dc7 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java @@ -899,7 +899,7 @@ public class MapActivityActions implements DialogProvider { } }).createItem()); - if (Version.isGooglePlayEnabled(app) || Version.isDeveloperVersion(app)) { + if (Version.isGooglePlayEnabled(app) || Version.isHuawei(app) || Version.isDeveloperVersion(app)) { optionsMenuHelper.addItem(new ItemBuilder().setTitleId(R.string.osm_live, mapActivity) .setId(DRAWER_OSMAND_LIVE_ID) .setIcon(R.drawable.ic_action_osm_live) diff --git a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java index 3929307cb9..47f7a17444 100644 --- a/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/OsmandInAppPurchaseActivity.java @@ -19,6 +19,7 @@ import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.Version; +import net.osmand.plus.download.DownloadActivity; import net.osmand.plus.inapp.InAppPurchaseHelper; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseInitCallback; import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener; @@ -54,8 +55,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In private void initInAppPurchaseHelper() { deinitInAppPurchaseHelper(); if (purchaseHelper == null) { - InAppPurchaseHelper purchaseHelper = getMyApplication().getInAppPurchaseHelper(); - if (isInAppPurchaseAllowed() && isInAppPurchaseSupported(purchaseHelper)) { + OsmandApplication app = getMyApplication(); + InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper(); + if (app.getSettings().isInternetConnectionAvailable() + && isInAppPurchaseAllowed() + && isInAppPurchaseSupported(purchaseHelper)) { this.purchaseHelper = purchaseHelper; } } @@ -128,13 +132,18 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In } } - public static void purchaseSrtmPlugin(@NonNull final Activity activity) { - OsmandPlugin plugin = OsmandPlugin.getPlugin(SRTMPlugin.class); - if(plugin == null || plugin.getInstallURL() == null) { - Toast.makeText(activity.getApplicationContext(), - activity.getString(R.string.activate_srtm_plugin), Toast.LENGTH_LONG).show(); - } else { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(plugin.getInstallURL()))); + public static void purchaseContourLines(@NonNull final Activity activity) { + OsmandApplication app = (OsmandApplication) activity.getApplication(); + if (app != null) { + InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper(); + if (purchaseHelper != null) { + app.logEvent("contour_lines_purchase_redirect"); + try { + purchaseHelper.purchaseContourLines(activity); + } catch (UnsupportedOperationException e) { + LOG.error("purchaseContourLines is not supported", e); + } + } } } @@ -201,6 +210,11 @@ public class OsmandInAppPurchaseActivity extends AppCompatActivity implements In } onInAppPurchaseItemPurchased(sku); fireInAppPurchaseItemPurchasedOnFragments(fragmentManager, sku, active); + if (purchaseHelper != null && purchaseHelper.getContourLines().getSku().equals(sku)) { + if (!(this instanceof MapActivity)) { + finish(); + } + } } public void fireInAppPurchaseItemPurchasedOnFragments(@NonNull FragmentManager fragmentManager, diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java index b2858b53b8..d3a48a5a15 100644 --- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanHillshadeSrtmDialogFragment.java @@ -79,7 +79,7 @@ public class ChoosePlanHillshadeSrtmDialogFragment extends ChoosePlanDialogFragm public void onClick(View v) { Activity activity = getActivity(); if (activity != null) { - OsmandInAppPurchaseActivity.purchaseSrtmPlugin(activity); + OsmandInAppPurchaseActivity.purchaseContourLines(activity); dismiss(); } } diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index e99bed63b7..1364d7b154 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -89,7 +89,8 @@ public abstract class InAppPurchaseHelper { REQUEST_INVENTORY, PURCHASE_FULL_VERSION, PURCHASE_LIVE_UPDATES, - PURCHASE_DEPTH_CONTOURS + PURCHASE_DEPTH_CONTOURS, + PURCHASE_CONTOUR_LINES } public abstract class InAppCommand { @@ -155,6 +156,10 @@ public abstract class InAppPurchaseHelper { return Version.isDeveloperBuild(ctx) || ctx.getSettings().DEPTH_CONTOURS_PURCHASED.get(); } + public static boolean isContourLinesPurchased(@NonNull OsmandApplication ctx) { + return Version.isDeveloperBuild(ctx) || ctx.getSettings().CONTOUR_LINES_PURCHASED.get(); + } + public InAppPurchases getInAppPurchases() { return purchases; } @@ -207,7 +212,7 @@ public abstract class InAppPurchaseHelper { } protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppCommand command) { - if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) { + if (isDeveloperVersion || (!Version.isGooglePlayEnabled(ctx) && !Version.isHuawei(ctx))) { notifyDismissProgress(taskType); stop(true); return; @@ -266,6 +271,8 @@ public abstract class InAppPurchaseHelper { public abstract void purchaseDepthContours(@NonNull final Activity activity) throws UnsupportedOperationException; + public abstract void purchaseContourLines(@NonNull final Activity activity) throws UnsupportedOperationException; + public abstract void manageSubscription(@NonNull Context ctx, @Nullable String sku); @SuppressLint("StaticFieldLeak") @@ -491,6 +498,17 @@ public abstract class InAppPurchaseHelper { notifyItemPurchased(getDepthContours().getSku(), false); stop(true); + } else if (info.getSku().equals(getContourLines().getSku())) { + // bought contour lines + getContourLines().setPurchaseState(PurchaseState.PURCHASED); + logDebug("Contours lines purchased."); + showToast(ctx.getString(R.string.contour_lines_thanks)); + ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true); + + notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_CONTOUR_LINES); + notifyItemPurchased(getContourLines().getSku(), false); + stop(true); + } else { notifyDismissProgress(activeTask); stop(true); diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java index e41c7d58df..82bf5667be 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java @@ -2008,6 +2008,7 @@ public class OsmandSettings { public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference("live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal(); public final OsmandPreference FULL_VERSION_PURCHASED = new BooleanPreference("billing_full_version_purchased", false).makeGlobal(); public final OsmandPreference DEPTH_CONTOURS_PURCHASED = new BooleanPreference("billing_sea_depth_purchased", false).makeGlobal(); + public final OsmandPreference CONTOUR_LINES_PURCHASED = new BooleanPreference("billing_srtm_purchased", false).makeGlobal(); public final OsmandPreference EMAIL_SUBSCRIBED = new BooleanPreference("email_subscribed", false).makeGlobal(); public final OsmandPreference DISCOUNT_ID = new IntPreference("discount_id", 0).makeGlobal(); diff --git a/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java b/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java index 808c541523..cd54b83948 100644 --- a/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java +++ b/OsmAnd/src/net/osmand/plus/srtmplugin/SRTMPlugin.java @@ -95,7 +95,9 @@ public class SRTMPlugin extends OsmandPlugin { @Override protected boolean pluginAvailable(OsmandApplication app) { - return super.pluginAvailable(app) || InAppPurchaseHelper.isSubscribedToLiveUpdates(app); + return super.pluginAvailable(app) + || InAppPurchaseHelper.isSubscribedToLiveUpdates(app) + || InAppPurchaseHelper.isContourLinesPurchased(app); } @Override From 1e4f630090f0f5315de03c16d15ee6695369b2bc Mon Sep 17 00:00:00 2001 From: max-klaus Date: Sun, 4 Oct 2020 16:48:11 +0300 Subject: [PATCH 73/75] Fix build.gradle (drop content) --- build.gradle | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/build.gradle b/build.gradle index e75ca7d5b8..a64480346c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,6 @@ buildscript { jcenter() maven { url 'https://developer.huawei.com/repo/' - content { - includeGroup 'com.huawei.agconnect' - includeGroup 'com.huawei.hms' - includeGroup 'com.huawei.hmf' - } } } dependencies { @@ -45,11 +40,6 @@ allprojects { } maven { url 'https://developer.huawei.com/repo/' - content { - includeGroup 'com.huawei.agconnect' - includeGroup 'com.huawei.hms' - includeGroup 'com.huawei.hmf' - } } } } From b3bdd7cf0487a97ae77a02f1237a43051e7a09b3 Mon Sep 17 00:00:00 2001 From: vshcherb Date: Sun, 4 Oct 2020 19:09:40 +0200 Subject: [PATCH 74/75] Update build.gradle --- OsmAnd/build.gradle | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index a87a05b117..2db548a71b 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -196,17 +196,16 @@ android { dimension "version" applicationId "net.osmand.plus" } - fulldev { - dimension "version" - applicationId "net.osmand.plus" - resConfig "en" - //resConfigs "xxhdpi", "nodpi" - } + fulldev { + dimension "version" + applicationId "net.osmand.plus" + resConfig "en" + // resConfigs "xxhdpi", "nodpi" + } freehuawei { dimension "version" applicationId "net.osmand.huawei" } - // CoreVersion legacy { dimension "coreversion" From fb15b53f7e1774f4774fe3f4d3871af86dbb6421 Mon Sep 17 00:00:00 2001 From: max-klaus Date: Sun, 4 Oct 2020 20:17:19 +0300 Subject: [PATCH 75/75] Fix build.gradle (buildType) --- OsmAnd/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 2db548a71b..9faf1b38aa 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -225,10 +225,11 @@ android { signingConfig signingConfigs.development } release { - productFlavors.all { flavor -> - flavor.signingConfig signingConfigs.publishing + if (gradle.startParameter.taskNames.toString().contains("huawei")) { + signingConfig signingConfigs.publishingHuawei + } else { + signingConfig signingConfigs.publishing } - productFlavors.freehuawei.signingConfig signingConfigs.publishingHuawei } }