diff --git a/patches/0001-routing-Online-car-router-backed-by-Google.patch b/patches/0001-routing-Online-car-router-backed-by-Google.patch index 1bd1818..82ac0f7 100644 --- a/patches/0001-routing-Online-car-router-backed-by-Google.patch +++ b/patches/0001-routing-Online-car-router-backed-by-Google.patch @@ -24,6 +24,10 @@ The API key is entered by the user in Android settings or the Qt routing dialog and stored under "GoogleApiKey" in the native settings store. Without a key nothing is requested, so the feature is inert by default. +The mode is also selectable from another app, not just from the routing UI: +"type=google" in an om://route deep link and "mode=g" (or "mode=google") in a +geo: NAVIGATE intent, the latter routing from the current position. + Also restores Route's move operations, which a user-declared copy constructor had silently suppressed, turning every std::move(route) into a deep copy of the geometry and segments. @@ -34,12 +38,15 @@ the geometry and segments. .../src/main/res/values/donottranslate.xml | 1 + android/app/src/main/res/values/strings.xml | 4 + android/app/src/main/res/xml/prefs_main.xml | 6 + + .../intent/GoogleAssistantIntentHandler.java | 2 + .../main/cpp/app/organicmaps/sdk/Router.cpp | 1 + .../main/java/app/organicmaps/sdk/Router.java | 3 +- .../java/app/organicmaps/sdk/util/Config.java | 15 + .../src/main/res/drawable/ic_google_route.xml | 27 + data/strings/strings.txt | 10 + libs/drape_frontend/route_renderer.cpp | 8 +- + libs/map/map_tests/mwm_url_tests.cpp | 9 + + libs/map/mwm_url.cpp | 3 +- libs/map/routing_manager.cpp | 66 +- .../platform_tests_support/CMakeLists.txt | 6 + .../android_test_stubs.cpp | 117 +++ @@ -61,7 +68,7 @@ the geometry and segments. .../routing_tests/polyline_matcher_test.cpp | 102 +++ qt/routing_settings_dialog.cpp | 14 + qt/routing_settings_dialog.hpp | 1 + - 33 files changed, 2264 insertions(+), 20 deletions(-) + 36 files changed, 2277 insertions(+), 21 deletions(-) create mode 100644 android/sdk/src/main/res/drawable/ic_google_route.xml create mode 100644 libs/platform/platform_tests_support/android_test_stubs.cpp create mode 100644 libs/routing/corridor_matcher.cpp @@ -75,10 +82,10 @@ the geometry and segments. create mode 100644 libs/routing/routing_tests/polyline_matcher_test.cpp diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java -index 0c7b760def..6772ccd027 100644 +index 88bdc76432..a80368cf87 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingPlanFragment.java -@@ -300,6 +300,7 @@ public class RoutingPlanFragment extends Fragment implements View.OnLayoutChange +@@ -303,6 +303,7 @@ public class RoutingPlanFragment extends Fragment implements View.OnLayoutChange setRouterClick(R.id.transit, Router.Transit); setRouterClick(R.id.bicycle, Router.Bicycle); setRouterClick(R.id.ruler, Router.Ruler); @@ -86,7 +93,7 @@ index 0c7b760def..6772ccd027 100644 } private void setRouterClick(@IdRes int buttonId, @NonNull Router router) -@@ -317,6 +318,7 @@ public class RoutingPlanFragment extends Fragment implements View.OnLayoutChange +@@ -320,6 +321,7 @@ public class RoutingPlanFragment extends Fragment implements View.OnLayoutChange case Transit -> R.id.transit; case Bicycle -> R.id.bicycle; case Ruler -> R.id.ruler; @@ -144,7 +151,7 @@ index 2bbf7b4e23..08cabc8914 100644 diff --git a/android/app/src/main/res/values/donottranslate.xml b/android/app/src/main/res/values/donottranslate.xml -index 99fa8cc422..e70c11766c 100644 +index 7f96355f8c..b49487766a 100644 --- a/android/app/src/main/res/values/donottranslate.xml +++ b/android/app/src/main/res/values/donottranslate.xml @@ -35,6 +35,7 @@ @@ -156,7 +163,7 @@ index 99fa8cc422..e70c11766c 100644 Information Transliteration diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml -index be4aab7ecf..25f16ccaab 100644 +index e264fbea57..ec4518655a 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -350,6 +350,10 @@ @@ -187,6 +194,19 @@ index 5a2f43b448..6f474a0581 100644 Router.Pedestrian; + case "b" -> Router.Bicycle; + case "r" -> Router.Transit; ++ // Online car routing via the Google Routes API, needs the API key from settings. ++ case "g", "google" -> Router.Google; + default -> Router.Vehicle; + }; + } diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/Router.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/Router.cpp index 01bb8af8c1..8922b06299 100644 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/Router.cpp @@ -273,10 +293,10 @@ index 0000000000..9df76770e8 + android:strokeColor="#000"/> + diff --git a/data/strings/strings.txt b/data/strings/strings.txt -index aae7e3bb69..5e9b088373 100644 +index 09f0693bdf..064a4c93c6 100644 --- a/data/strings/strings.txt +++ b/data/strings/strings.txt -@@ -12050,6 +12050,16 @@ vi = yer i̇mi +@@ -12008,6 +12008,16 @@ vi = yer i̇mi zh-Hans = 书签 zh-Hant = 書籤 @@ -312,8 +332,49 @@ index dac43afa36..f5e1413e64 100644 // Adjust line color depending on route type and subroute distance. After the first stop point // route color is adjusted according to RouteMaskCar, RouteMaskBicycle or RouteMaskPedestrian properties. +diff --git a/libs/map/map_tests/mwm_url_tests.cpp b/libs/map/map_tests/mwm_url_tests.cpp +index d46b9825dc..85cb433615 100644 +--- a/libs/map/map_tests/mwm_url_tests.cpp ++++ b/libs/map/map_tests/mwm_url_tests.cpp +@@ -59,6 +59,15 @@ UNIT_TEST(RouteApiSmoke) + TEST_EQUAL(test.GetRoutingType(), "vehicle", ()); + } + ++UNIT_TEST(RouteApiGoogleType) ++{ ++ // Mixed case to cover the lowercasing of the "type" value. ++ ParsedMapApi test("om://route?sll=1,1&saddr=name0&dll=2,2&daddr=name1&type=Google"); ++ TEST_EQUAL(test.GetRequestType(), UrlType::Route, ()); ++ TEST_EQUAL(test.GetRoutePoints().size(), 2, ()); ++ TEST_EQUAL(test.GetRoutingType(), "google", ()); ++} ++ + UNIT_TEST(SearchApiSmoke) + { + string const urlString = +diff --git a/libs/map/mwm_url.cpp b/libs/map/mwm_url.cpp +index 36ab190442..036f7afa9c 100644 +--- a/libs/map/mwm_url.cpp ++++ b/libs/map/mwm_url.cpp +@@ -56,6 +56,7 @@ std::string_view constexpr kRouteTypeVehicle = "vehicle"; + std::string_view constexpr kRouteTypePedestrian = "pedestrian"; + std::string_view constexpr kRouteTypeBicycle = "bicycle"; + std::string_view constexpr kRouteTypeTransit = "transit"; ++std::string_view constexpr kRouteTypeGoogle = "google"; + } // namespace route + + namespace search +@@ -380,7 +381,7 @@ void ParsedMapApi::ParseRouteParam(std::string const & key, std::string const & + { + std::string const lowerValue = strings::MakeLowerCase(value); + if (lowerValue == kRouteTypePedestrian || lowerValue == kRouteTypeVehicle || lowerValue == kRouteTypeBicycle || +- lowerValue == kRouteTypeTransit) ++ lowerValue == kRouteTypeTransit || lowerValue == kRouteTypeGoogle) + { + m_routingType = lowerValue; + } diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp -index 6ca3295189..0e206b6ad1 100644 +index 3b1f8da65b..c629338ef2 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -4,6 +4,7 @@ @@ -850,7 +911,7 @@ index 0000000000..5a2ed19a31 +} // namespace routing diff --git a/libs/routing/google_router.cpp b/libs/routing/google_router.cpp new file mode 100644 -index 0000000000..b88c67413d +index 0000000000..84f419978f --- /dev/null +++ b/libs/routing/google_router.cpp @@ -0,0 +1,748 @@ @@ -1489,8 +1550,8 @@ index 0000000000..b88c67413d +GoogleRouter::~GoogleRouter() = default; + +RouterResultCode GoogleRouter::CalculateRoute(Checkpoints const & checkpoints, m2::PointD const & startDirection, -+ bool /* adjustToPrevRoute */, RouterDelegate const & delegate, -+ RoutesResult & result) ++ bool /* adjustToPrevRoute */, bool /* needAlternatives */, ++ RouterDelegate const & delegate, RoutesResult & result) +{ + std::string apiKey; + if (!settings::Get(kGoogleApiKeySetting, apiKey) || apiKey.empty()) @@ -1604,7 +1665,7 @@ index 0000000000..b88c67413d +} // namespace routing diff --git a/libs/routing/google_router.hpp b/libs/routing/google_router.hpp new file mode 100644 -index 0000000000..11dc0489ae +index 0000000000..dddd5ba1b0 --- /dev/null +++ b/libs/routing/google_router.hpp @@ -0,0 +1,104 @@ @@ -1655,7 +1716,7 @@ index 0000000000..11dc0489ae + void SetGuides(GuidesTracks && /* guides */) override {} + + RouterResultCode CalculateRoute(Checkpoints const & checkpoints, m2::PointD const & startDirection, -+ bool adjustToPrevRoute, RouterDelegate const & delegate, ++ bool adjustToPrevRoute, bool needAlternatives, RouterDelegate const & delegate, + RoutesResult & result) override; + + bool FindClosestProjectionToRoad(m2::PointD const & point, m2::PointD const & direction, double radius, @@ -1713,7 +1774,7 @@ index 0000000000..11dc0489ae + std::vector const & intervals); +} // namespace routing diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp -index 04aa38d041..d19d31d88b 100644 +index f794e13398..1f9067bb1e 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -3,6 +3,7 @@ @@ -1732,9 +1793,9 @@ index 04aa38d041..d19d31d88b 100644 #include "routing/route.hpp" #include "routing/routing_helpers.hpp" #include "routing/routing_options.hpp" -@@ -74,6 +76,12 @@ double constexpr kLeapsStageContribution = 0.15; - double constexpr kCandidatesStageContribution = 0.55; - double constexpr kAlmostZeroContribution = 1e-7; +@@ -99,6 +101,12 @@ EdgeEstimator::Strategy OtherStrategy(EdgeEstimator::Strategy strategy) + : EdgeEstimator::Strategy::Normal; + } +// Corridor-constrained map matching of an external reference polyline (see +// IndexRouter::CalculateRouteAlongPolyline). A matched leg is rejected if any of its vertices is farther than this from @@ -1745,7 +1806,7 @@ index 04aa38d041..d19d31d88b 100644 // If user left the route within this range(meters), adjust the route. Else full rebuild. double constexpr kAdjustRangeM = 5000.0; // Full rebuild if distance(meters) is less. -@@ -824,6 +832,174 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints, +@@ -869,6 +877,174 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints, return RouterResultCode::NoError; } @@ -1921,7 +1982,7 @@ index 04aa38d041..d19d31d88b 100644 IndexGraphStarterJoints & jointStarter) { diff --git a/libs/routing/index_router.hpp b/libs/routing/index_router.hpp -index f6ceed8fde..2fa2aa406b 100644 +index 25c1044777..f3da73cb0f 100644 --- a/libs/routing/index_router.hpp +++ b/libs/routing/index_router.hpp @@ -87,6 +87,21 @@ public: @@ -2085,7 +2146,7 @@ index 0dbc032c9b..88dad51fdf 100644 ASSERT(false, ("Incorrect routing string:", str)); return RouterType::Vehicle; diff --git a/libs/routing/router.hpp b/libs/routing/router.hpp -index 41f89a8e4c..a946a03ab1 100644 +index eedf8374f8..a2d43351de 100644 --- a/libs/routing/router.hpp +++ b/libs/routing/router.hpp @@ -37,6 +37,7 @@ enum class RouterType diff --git a/patches/0002-search-Online-place-search-backed-by-Google.patch b/patches/0002-search-Online-place-search-backed-by-Google.patch index 0cdbfc4..d66c4e1 100644 --- a/patches/0002-search-Online-place-search-backed-by-Google.patch +++ b/patches/0002-search-Online-place-search-backed-by-Google.patch @@ -18,7 +18,7 @@ place-page section is needed for them. Shares the "GoogleApiKey" setting with the Google router; without a key nothing is requested. --- - .../organicmaps/search/SearchFragment.java | 55 +- + .../organicmaps/search/SearchFragment.java | 62 +- .../search/SearchPageViewModel.java | 18 + .../layout/toolbar_search_controls_sheet.xml | 9 + android/app/src/main/res/values/strings.xml | 4 + @@ -26,31 +26,31 @@ nothing is requested. .../organicmaps/sdk/search/SearchEngine.java | 23 + data/strings/strings.txt | 10 + libs/map/CMakeLists.txt | 2 + - libs/map/framework.cpp | 21 + + libs/map/framework.cpp | 22 + libs/map/framework.hpp | 9 + - libs/map/google_places.cpp | 605 ++++++++++++++++++ - libs/map/google_places.hpp | 138 ++++ + libs/map/google_places.cpp | 604 ++++++++++++++++++ + libs/map/google_places.hpp | 136 ++++ libs/map/map_tests/CMakeLists.txt | 1 + libs/map/map_tests/google_places_tests.cpp | 261 ++++++++ libs/map/place_page_info.hpp | 11 + - 15 files changed, 1210 insertions(+), 3 deletions(-) + 15 files changed, 1215 insertions(+), 3 deletions(-) create mode 100644 libs/map/google_places.cpp create mode 100644 libs/map/google_places.hpp create mode 100644 libs/map/map_tests/google_places_tests.cpp diff --git a/android/app/src/main/java/app/organicmaps/search/SearchFragment.java b/android/app/src/main/java/app/organicmaps/search/SearchFragment.java -index 30633d258b..9619d3c9f8 100644 +index eb68ba5767..a20f3cc3cb 100644 --- a/android/app/src/main/java/app/organicmaps/search/SearchFragment.java +++ b/android/app/src/main/java/app/organicmaps/search/SearchFragment.java -@@ -2,6 +2,7 @@ package app.organicmaps.search; - +@@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter; + import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; +import android.content.res.ColorStateList; import android.location.Location; import android.os.Bundle; import android.os.Handler; -@@ -10,6 +11,7 @@ import android.text.TextUtils; +@@ -13,6 +14,7 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -58,15 +58,15 @@ index 30633d258b..9619d3c9f8 100644 import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CallSuper; -@@ -17,6 +19,7 @@ import androidx.annotation.NonNull; +@@ -20,6 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.core.widget.ImageViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; - import androidx.lifecycle.Observer; -@@ -38,11 +41,13 @@ import app.organicmaps.sdk.search.SearchResult; + import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +@@ -47,11 +50,13 @@ import app.organicmaps.sdk.search.SearchResult; import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.Language; import app.organicmaps.sdk.util.SharedPropertiesUtils; @@ -80,7 +80,7 @@ index 30633d258b..9619d3c9f8 100644 import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; import java.util.Arrays; -@@ -87,7 +92,7 @@ public class SearchFragment extends Fragment implements SearchListener, Categori +@@ -107,7 +112,7 @@ public class SearchFragment extends Fragment implements SearchListener, Categori private WindowInsetsCompat mLastKnownInsets = null; @NonNull @@ -89,7 +89,7 @@ index 30633d258b..9619d3c9f8 100644 private final RecyclerView.OnScrollListener mRecyclerListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) -@@ -110,6 +115,10 @@ public class SearchFragment extends Fragment implements SearchListener, Categori +@@ -130,6 +135,10 @@ public class SearchFragment extends Fragment implements SearchListener, Categori final boolean wasEnabled = Boolean.TRUE.equals(mSearchPreviouslyEnabled); mSearchPreviouslyEnabled = enabled; @@ -100,7 +100,21 @@ index 30633d258b..9619d3c9f8 100644 if (!enabled) { if (!wasEnabled) -@@ -569,8 +578,13 @@ public class SearchFragment extends Fragment implements SearchListener, Categori +@@ -680,6 +689,13 @@ public class SearchFragment extends Fragment implements SearchListener, Categori + final RoutingController controller = RoutingController.get(); + if (controller.isWaitingPoiPick()) + { ++ // A Google prediction has no coordinates until its details are fetched, so open its place page and ++ // let the user commit the pick from there. ++ if (mSearchViewModel.isGoogleSearchEnabled()) ++ { ++ SearchEngine.INSTANCE.showResult(resultIndex); ++ return; ++ } + // Pass an unnamed result through with an empty title: the route point then shows the feature type + // once, instead of repeating it as its own subtitle. + final String subtitle = result.description == null ? "" : result.description.localizedFeatureType; +@@ -752,8 +768,13 @@ public class SearchFragment extends Fragment implements SearchListener, Categori mSearchViewModel.clearPendingRequest(); SearchEngine.INSTANCE.setQuery(getQuery()); @@ -116,7 +130,7 @@ index 30633d258b..9619d3c9f8 100644 if (!started) { stopSearch(); -@@ -856,11 +870,46 @@ public class SearchFragment extends Fragment implements SearchListener, Categori +@@ -1036,11 +1057,46 @@ public class SearchFragment extends Fragment implements SearchListener, Categori private class ToolbarController extends SearchToolbarController { @@ -205,10 +219,10 @@ index 817fdf40a8..c1fdd5a5dd 100644 mSearchPageLastState.setValue(BottomSheetBehavior.STATE_HIDDEN); mSearchEnabled.setValue(enabled); diff --git a/android/app/src/main/res/layout/toolbar_search_controls_sheet.xml b/android/app/src/main/res/layout/toolbar_search_controls_sheet.xml -index 68aaf11cb5..8ce33ad6a9 100644 +index 66ae864094..4a821aee4a 100644 --- a/android/app/src/main/res/layout/toolbar_search_controls_sheet.xml +++ b/android/app/src/main/res/layout/toolbar_search_controls_sheet.xml -@@ -81,6 +81,15 @@ +@@ -106,6 +106,15 @@ android:gravity="center_vertical" /> @@ -225,7 +239,7 @@ index 68aaf11cb5..8ce33ad6a9 100644 When following the route, please keep in mind: diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp -index 02bb7f2784..ff3e5d0889 100644 +index 22436d23a7..2d2cc0c957 100644 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/search/SearchEngine.cpp @@ -4,6 +4,7 @@ @@ -259,7 +273,7 @@ index 02bb7f2784..ff3e5d0889 100644 #include "base/assert.hpp" #include "base/logging.hpp" -@@ -216,6 +218,19 @@ void OnBookmarksSearchResults(search::BookmarksSearchParams::Results results, +@@ -213,6 +215,19 @@ void OnBookmarksSearchResults(search::BookmarksSearchParams::Results results, env->CallVoidMethod(g_javaListener, method, jResults.get(), static_cast(timestamp)); } @@ -279,7 +293,7 @@ index 02bb7f2784..ff3e5d0889 100644 } // namespace extern "C" -@@ -314,6 +329,30 @@ JNIEXPORT jboolean Java_app_organicmaps_sdk_search_SearchEngine_nativeRunInterac +@@ -311,6 +326,30 @@ JNIEXPORT jboolean Java_app_organicmaps_sdk_search_SearchEngine_nativeRunInterac return JNI_TRUE; } @@ -299,8 +313,8 @@ index 02bb7f2784..ff3e5d0889 100644 + // isMapAndTable is false as well - there is no viewport search to poke in this mode. + auto const started = fr->GetGooglePlaces().Autocomplete( + jni::ToNativeString(env, bytes), jni::ToNativeString(env, lang), origin, -+ std::bind(&OnResults, std::placeholders::_1, std::placeholders::_2, timestamp, false /* isMapAndTable */, -+ false /* hasPosition */, 0.0, 0.0)); ++ std::bind(&OnResults, std::placeholders::_1, timestamp, false /* isMapAndTable */, false /* hasPosition */, ++ 0.0, 0.0)); + + if (started) + g_queryTimestamp = timestamp; @@ -310,7 +324,7 @@ index 02bb7f2784..ff3e5d0889 100644 JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeRunSearchMaps(JNIEnv * env, jclass clazz, jbyteArray bytes, jstring lang, jlong timestamp) -@@ -343,6 +382,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeShowResult(JNI +@@ -340,6 +379,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeShowResult(JNI { if (index < 0 || index >= static_cast(g_results.GetCount())) return; @@ -319,16 +333,16 @@ index 02bb7f2784..ff3e5d0889 100644 g_framework->NativeFramework()->ShowSearchResult(g_results[index]); } -@@ -354,6 +395,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeSelectResult(J - auto const mode = g_framework->GetMyPositionMode(); - if (mode == location::Follow || mode == location::FollowAndRotate) - g_framework->NativeFramework()->StopLocationFollow(); +@@ -347,6 +388,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeSelectResult(J + { + if (index < 0 || index >= static_cast(g_results.GetCount())) + return; + if (ResolveGooglePrediction(index)) + return; g_framework->NativeFramework()->SelectSearchResult(g_results[index], true /* animation */); } -@@ -371,5 +414,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelInteract +@@ -364,5 +407,8 @@ JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelInteract JNIEXPORT void Java_app_organicmaps_sdk_search_SearchEngine_nativeCancelAllSearches(JNIEnv * env, jclass clazz) { g_framework->NativeFramework()->GetSearchAPI().CancelAllSearches(); @@ -338,7 +352,7 @@ index 02bb7f2784..ff3e5d0889 100644 } } // extern "C" diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/search/SearchEngine.java b/android/sdk/src/main/java/app/organicmaps/sdk/search/SearchEngine.java -index 332321ab5e..6b8eadcf2a 100644 +index a9744a23fa..fbad449ffa 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/search/SearchEngine.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/search/SearchEngine.java @@ -149,6 +149,23 @@ public enum SearchEngine implements SearchListener, MapSearchListener, @@ -379,10 +393,10 @@ index 332321ab5e..6b8eadcf2a 100644 * @param bytes utf-8 formatted query bytes */ diff --git a/data/strings/strings.txt b/data/strings/strings.txt -index 5e9b088373..e0b8f07c69 100644 +index 064a4c93c6..9af31fa32d 100644 --- a/data/strings/strings.txt +++ b/data/strings/strings.txt -@@ -12060,6 +12060,16 @@ comment = Settings: explanation under the Google Maps API key input field +@@ -12018,6 +12018,16 @@ comment = Settings: explanation under the Google Maps API key input field tags = android-app en = Required to use the Google routing mode @@ -400,10 +414,10 @@ index 5e9b088373..e0b8f07c69 100644 [dialog_routing_disclaimer_title] diff --git a/libs/map/CMakeLists.txt b/libs/map/CMakeLists.txt -index 32613a5b61..420ae38df5 100644 +index 5930cac6b0..0720bee254 100644 --- a/libs/map/CMakeLists.txt +++ b/libs/map/CMakeLists.txt -@@ -25,6 +25,8 @@ set(SRC +@@ -21,6 +21,8 @@ set(SRC features_fetcher.hpp framework.cpp framework.hpp @@ -413,10 +427,10 @@ index 32613a5b61..420ae38df5 100644 gps_track_collection.hpp gps_track_filter.cpp diff --git a/libs/map/framework.cpp b/libs/map/framework.cpp -index ec1e788fa3..dd7dc4de13 100644 +index 192d95f5bf..22034a5f9e 100644 --- a/libs/map/framework.cpp +++ b/libs/map/framework.cpp -@@ -1530,6 +1530,27 @@ void Framework::SelectSearchResult(search::Result const & result, bool animation +@@ -1532,6 +1532,28 @@ void Framework::SelectSearchResult(search::Result const & result, bool animation ActivateMapSelection(); } @@ -429,6 +443,7 @@ index ec1e788fa3..dd7dc4de13 100644 + // Nothing here matches a feature in the map data, so don't let the place page snap to a nearby one. + info.m_match = place_page::BuildInfo::Match::Nothing; + ++ StopLocationFollow(); + m_currentPlacePageInfo = BuildPlacePageInfo(info); + m_currentPlacePageInfo->SetCustomNames(place.m_name, place.m_subtitle); + m_currentPlacePageInfo->SetUiAddress(place.m_address); @@ -445,7 +460,7 @@ index ec1e788fa3..dd7dc4de13 100644 { GetSearchAPI().CancelAllSearches(); diff --git a/libs/map/framework.hpp b/libs/map/framework.hpp -index c96f5642e0..eeaa9c5b38 100644 +index 9360a92f6a..6ea04089aa 100644 --- a/libs/map/framework.hpp +++ b/libs/map/framework.hpp @@ -4,6 +4,7 @@ @@ -474,8 +489,8 @@ index c96f5642e0..eeaa9c5b38 100644 /// @name Bookmarks, Tracks and other UserMarks /// Scans and loads all kml files with bookmarks in WritableDir. -@@ -503,6 +508,10 @@ public: - // Moves viewport to the search result and taps on it. +@@ -505,6 +510,10 @@ public: + // without cancelling searches or clearing their marks. void SelectSearchResult(search::Result const & res, bool animation); + // Moves viewport to |place| and opens a place page there, filled from |place| rather than from the @@ -487,10 +502,10 @@ index c96f5642e0..eeaa9c5b38 100644 void SelectRoute(uint32_t relID); diff --git a/libs/map/google_places.cpp b/libs/map/google_places.cpp new file mode 100644 -index 0000000000..fcf18cbfd4 +index 0000000000..3506c37e07 --- /dev/null +++ b/libs/map/google_places.cpp -@@ -0,0 +1,605 @@ +@@ -0,0 +1,604 @@ +#include "map/google_places.hpp" + +#include "routing/google_router.hpp" @@ -813,8 +828,7 @@ index 0000000000..fcf18cbfd4 + return; + + m_placeIds = std::move(placeIds); -+ std::vector productInfo(results.GetCount()); -+ onResults(std::move(results), std::move(productInfo)); ++ onResults(std::move(results)); + }); + }); + @@ -1098,14 +1112,12 @@ index 0000000000..fcf18cbfd4 +} // namespace search diff --git a/libs/map/google_places.hpp b/libs/map/google_places.hpp new file mode 100644 -index 0000000000..50bbfaaa09 +index 0000000000..44dfab8803 --- /dev/null +++ b/libs/map/google_places.hpp -@@ -0,0 +1,138 @@ +@@ -0,0 +1,136 @@ +#pragma once + -+#include "map/search_product_info.hpp" -+ +#include "search/result.hpp" + +#include "indexer/feature_meta.hpp" @@ -1141,7 +1153,7 @@ index 0000000000..50bbfaaa09 +class GooglePlaces +{ +public: -+ using OnResults = std::function)>; ++ using OnResults = std::function; + // Invoked on the GUI thread, only when the details request succeeded. + using OnPlace = std::function; + using RunUITaskFn = std::function)>; @@ -1241,17 +1253,17 @@ index 0000000000..50bbfaaa09 +PlaceDetails ParsePlaceDetailsResponse(std::string const & json); +} // namespace search diff --git a/libs/map/map_tests/CMakeLists.txt b/libs/map/map_tests/CMakeLists.txt -index 6abca4c1aa..1cbae92ef4 100644 +index 6b868874c9..0d6375622f 100644 --- a/libs/map/map_tests/CMakeLists.txt +++ b/libs/map/map_tests/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRC - editor_notes_tests.cpp extrapolator_tests.cpp feature_getters_tests.cpp + framework_viewport_tests.cpp + google_places_tests.cpp gps_track_collection_test.cpp + gps_track_filter_test.cpp gps_track_storage_test.cpp - gps_track_test.cpp diff --git a/libs/map/map_tests/google_places_tests.cpp b/libs/map/map_tests/google_places_tests.cpp new file mode 100644 index 0000000000..f3ac244ee9 diff --git a/patches/0003-garmin-Turn-by-turn-navigation-on-watch.patch b/patches/0003-garmin-Turn-by-turn-navigation-on-watch.patch index 969e7b2..90ffc45 100644 --- a/patches/0003-garmin-Turn-by-turn-navigation-on-watch.patch +++ b/patches/0003-garmin-Turn-by-turn-navigation-on-watch.patch @@ -70,10 +70,10 @@ the watch app is not part of the Gradle build. create mode 100644 garmin/tools/make_launcher_icon.py diff --git a/.gitignore b/.gitignore -index caee2c8c72..e341679747 100644 +index 986146f95c..1211d8f8c1 100644 --- a/.gitignore +++ b/.gitignore -@@ -105,3 +105,8 @@ CMakeUserPresets.json +@@ -102,3 +102,8 @@ CMakeUserPresets.json /screenshots/ /keywords/ /iphone/metadata/**/keywords.txt @@ -95,10 +95,10 @@ index ee2c54156b..df434fb910 100644 implementation project(':libs:routing') implementation project(':libs:utils') diff --git a/android/app/src/main/java/app/organicmaps/MwmApplication.java b/android/app/src/main/java/app/organicmaps/MwmApplication.java -index c7a8606353..da218664a2 100644 +index 17fc737d25..e62836cf68 100644 --- a/android/app/src/main/java/app/organicmaps/MwmApplication.java +++ b/android/app/src/main/java/app/organicmaps/MwmApplication.java -@@ -20,6 +20,7 @@ import androidx.lifecycle.ProcessLifecycleOwner; +@@ -21,6 +21,7 @@ import androidx.lifecycle.ProcessLifecycleOwner; import androidx.preference.PreferenceManager; import app.organicmaps.background.OsmUploadWork; import app.organicmaps.downloader.DownloaderNotifier; @@ -106,7 +106,7 @@ index c7a8606353..da218664a2 100644 import app.organicmaps.location.TrackRecordingService; import app.organicmaps.routing.NavigationService; import app.organicmaps.sdk.Map; -@@ -243,5 +244,9 @@ public class MwmApplication extends Application implements Application.ActivityL +@@ -271,5 +272,9 @@ public class MwmApplication extends Application implements Application.ActivityL final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); NavigationService.setOpenAppPendingIntent(pendingIntent); @@ -117,7 +117,7 @@ index c7a8606353..da218664a2 100644 } } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml -index 7e06f8fc2d..1b6f557929 100644 +index a265131802..cffbd68910 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -30,6 +30,9 @@ androidx-work = "2.10.5" @@ -130,7 +130,7 @@ index 7e06f8fc2d..1b6f557929 100644 # firebase-crashlytics-ndk requires minSdkVersion 23 since 34.0.0 # See https://firebase.google.com/support/release-notes/android#crashlytics-ndk_v20-0-0 # Use 33.11.0 instead of 33.16.0 because of the https://github.com/firebase/firebase-android-sdk/issues/6993 -@@ -83,6 +86,8 @@ androidx-work = { module = "androidx.work:work-runtime", version.ref = "androidx +@@ -80,6 +83,8 @@ androidx-work = { module = "androidx.work:work-runtime", version.ref = "androidx detekt-cli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = "detekt" } devnullorthrow-mpandroidchart = { module = "com.github.devnullorthrow:MPAndroidChart", version.ref = "devnullorthrow-mpandroidchart" } @@ -1686,10 +1686,10 @@ index 0000000000..6cf0d3ac78 + void onNavigationStopped(boolean arrived); +} diff --git a/android/libs/routing/src/main/java/app/organicmaps/routing/NavigationService.java b/android/libs/routing/src/main/java/app/organicmaps/routing/NavigationService.java -index a43b9a1515..3a754dee17 100644 +index 74001547ff..cffb4d5991 100644 --- a/android/libs/routing/src/main/java/app/organicmaps/routing/NavigationService.java +++ b/android/libs/routing/src/main/java/app/organicmaps/routing/NavigationService.java -@@ -54,6 +54,14 @@ public class NavigationService extends Service implements LocationListener +@@ -53,6 +53,14 @@ public class NavigationService extends Service implements LocationListener private static NotificationCompat.Extender sCarNotificationExtender; @Nullable private static PendingIntent sOpenAppPendingIntent; @@ -1704,7 +1704,7 @@ index a43b9a1515..3a754dee17 100644 @SuppressWarnings("NotNullFieldNotInitialized") @NonNull -@@ -83,6 +91,65 @@ public class NavigationService extends Service implements LocationListener +@@ -82,6 +90,65 @@ public class NavigationService extends Service implements LocationListener sOpenAppPendingIntent = openAppPendingIntent; } @@ -1770,7 +1770,7 @@ index a43b9a1515..3a754dee17 100644 /** * Start the foreground service for turn-by-turn voice-guided navigation. * -@@ -185,6 +252,7 @@ public class NavigationService extends Service implements LocationListener +@@ -184,6 +251,7 @@ public class NavigationService extends Service implements LocationListener Logger.i(TAG); mNotificationBuilder = null; @@ -1778,7 +1778,7 @@ index a43b9a1515..3a754dee17 100644 sOrganicMaps.getLocationHelper().removeListener(this); TtsPlayer.INSTANCE.stop(); -@@ -248,6 +316,8 @@ public class NavigationService extends Service implements LocationListener +@@ -247,6 +315,8 @@ public class NavigationService extends Service implements LocationListener return START_NOT_STICKY; } @@ -1787,15 +1787,15 @@ index a43b9a1515..3a754dee17 100644 final LocationHelper locationHelper = sOrganicMaps.getLocationHelper(); // Subscribe to location updates. This call is idempotent. -@@ -288,6 +358,7 @@ public class NavigationService extends Service implements LocationListener +@@ -286,6 +356,7 @@ public class NavigationService extends Service implements LocationListener // This check should be done after playTurnNotifications() to play the last turn notification. if (Framework.nativeIsRouteFinished()) { + mRouteFinished = true; routingController.cancel(); - sOrganicMaps.getLocationHelper().restartWithNewMode(); stopSelf(); -@@ -298,6 +369,10 @@ public class NavigationService extends Service implements LocationListener + return; +@@ -295,6 +366,10 @@ public class NavigationService extends Service implements LocationListener if (routingInfo == null) return; @@ -1807,7 +1807,7 @@ index a43b9a1515..3a754dee17 100644 mPlayer.playback(R.raw.speed_cams_beep); diff --git a/android/settings.gradle b/android/settings.gradle -index af8bd1cd38..cfe0ecab07 100644 +index 974662a7a5..3fb4681a1c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -21,6 +21,7 @@ include ':libs:api' diff --git a/upstream.tag b/upstream.tag index dc3073c..2128f84 100644 --- a/upstream.tag +++ b/upstream.tag @@ -1,2 +1,2 @@ -2026.08.27-18-android -ebcfd753f32ef8bbc8d205f29040dcba8a1159ed +2026.09.29-32-android +1253338dcd7bc6c1871e4a613468187e1d1e93e6