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