Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaria Volvenkova <d.volvenkova@corp.mail.ru>2018-10-05 13:44:20 +0300
committerAleksandr Zatsepin <alexzatsepin@users.noreply.github.com>2018-10-24 19:20:02 +0300
commit8ab3a1f5bf39bb6fa148ecf605d11583b37c82b5 (patch)
tree4ae7144fba533fad2ec0ec5c87dfa20ecbd75d7e
parent67185059a3813c91b63503bfd5c1e9c20b92b63e (diff)
Local ads features processing for geofencing.
-rw-r--r--android/jni/com/mapswithme/maps/LightFramework.cpp41
-rw-r--r--android/src/com/mapswithme/maps/Framework.java3
-rw-r--r--android/src/com/mapswithme/maps/LightFramework.java8
-rw-r--r--android/src/com/mapswithme/maps/api/GeoFenceFeature.java23
-rw-r--r--android/src/com/mapswithme/maps/background/NotificationService.java11
-rw-r--r--local_ads/event.hpp3
-rw-r--r--local_ads/statistics.cpp8
-rw-r--r--local_ads/statistics.hpp2
-rw-r--r--map/framework.cpp1
-rw-r--r--map/framework_light.hpp44
-rw-r--r--map/local_ads_manager.cpp516
-rw-r--r--map/local_ads_manager.hpp75
12 files changed, 596 insertions, 139 deletions
diff --git a/android/jni/com/mapswithme/maps/LightFramework.cpp b/android/jni/com/mapswithme/maps/LightFramework.cpp
index 6e97e07f01..aceca8849a 100644
--- a/android/jni/com/mapswithme/maps/LightFramework.cpp
+++ b/android/jni/com/mapswithme/maps/LightFramework.cpp
@@ -1,4 +1,5 @@
#include "map/framework_light.hpp"
+#include "map/local_ads_manager.hpp"
#include "com/mapswithme/core/jni_helper.hpp"
@@ -19,4 +20,44 @@ Java_com_mapswithme_maps_LightFramework_nativeGetNumberUnsentUGC(JNIEnv * env, j
Framework const framework(REQUEST_TYPE_NUMBER_OF_UNSENT_UGC);
return static_cast<jint>(framework.Get<REQUEST_TYPE_NUMBER_OF_UNSENT_UGC>());
}
+
+JNIEXPORT jobjectArray JNICALL
+Java_com_mapswithme_maps_LightFramework_nativeGetLocalAdsFeatures(JNIEnv * env, jclass clazz, jdouble lat, jdouble lon,
+ jdouble radiusInMeters, jint maxCount)
+{
+ Framework framework(REQUEST_TYPE_LOCAL_ADS_FEATURES);
+ auto const features = framework.Get<REQUEST_TYPE_LOCAL_ADS_FEATURES>(lat, lon, radiusInMeters, maxCount);
+
+ static jclass const geoFenceFeatureClazz = jni::GetGlobalClassRef(env,
+ "com/mapswithme/maps/api/GeoFenceFeature");
+ // Java signature : GeoFenceFeature(long mwmVersion, String countryId, int featureIndex,
+ // double latitude, double longitude)
+ static jmethodID const geoFenceFeatureConstructor =
+ jni::GetConstructorID(env, geoFenceFeatureClazz, "(JLjava/lang/String;IDD)V");
+
+ return jni::ToJavaArray(env, geoFenceFeatureClazz, features, [&](JNIEnv * jEnv, CampaignFeature const & data)
+ {
+ jni::TScopedLocalRef const countryId(env, jni::ToJavaString(env, data.m_countryId));
+ return env->NewObject(geoFenceFeatureClazz, geoFenceFeatureConstructor,
+ static_cast<jlong>(data.m_mwmVersion),
+ countryId.get(),
+ static_cast<jint>(data.m_featureIndex),
+ static_cast<jdouble>(data.m_lat),
+ static_cast<jdouble>(data.m_lon));
+ });
+}
+
+JNIEXPORT void JNICALL
+Java_com_mapswithme_maps_LightFramework_nativeLogLocalAdsEvent(JNIEnv * env, jclass clazz, jint type,
+ jdouble lat, jdouble lon, jint accuracyInMeters,
+ jlong mwmVersion, jstring countryId, jint featureIndex)
+{
+ Framework framework(REQUEST_TYPE_LOCAL_ADS_STATISTICS);
+ local_ads::Event event(static_cast<local_ads::EventType>(type), static_cast<long>(mwmVersion),
+ jni::ToNativeString(env, countryId), static_cast<uint32_t>(featureIndex),
+ static_cast<uint8_t>(1) /* zoom level*/, local_ads::Clock::now(),
+ static_cast<double>(lat), static_cast<double>(lon),
+ static_cast<uint16_t>(accuracyInMeters));
+ framework.Get<REQUEST_TYPE_LOCAL_ADS_STATISTICS>()->RegisterEvent(std::move(event));
+}
} // extern "C"
diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java
index 8f3f17d63a..4c7eb33f74 100644
--- a/android/src/com/mapswithme/maps/Framework.java
+++ b/android/src/com/mapswithme/maps/Framework.java
@@ -75,13 +75,14 @@ public class Framework
@Retention(RetentionPolicy.SOURCE)
@IntDef({LOCAL_ADS_EVENT_SHOW_POINT, LOCAL_ADS_EVENT_OPEN_INFO, LOCAL_ADS_EVENT_CLICKED_PHONE,
- LOCAL_ADS_EVENT_CLICKED_WEBSITE})
+ LOCAL_ADS_EVENT_CLICKED_WEBSITE, LOCAL_ADS_EVENT_VISIT})
public @interface LocalAdsEventType {}
public static final int LOCAL_ADS_EVENT_SHOW_POINT = 0;
public static final int LOCAL_ADS_EVENT_OPEN_INFO = 1;
public static final int LOCAL_ADS_EVENT_CLICKED_PHONE = 2;
public static final int LOCAL_ADS_EVENT_CLICKED_WEBSITE = 3;
+ public static final int LOCAL_ADS_EVENT_VISIT = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ROUTE_REBUILD_AFTER_POINTS_LOADING})
diff --git a/android/src/com/mapswithme/maps/LightFramework.java b/android/src/com/mapswithme/maps/LightFramework.java
index c3586a6a6c..f75b329482 100644
--- a/android/src/com/mapswithme/maps/LightFramework.java
+++ b/android/src/com/mapswithme/maps/LightFramework.java
@@ -1,7 +1,15 @@
package com.mapswithme.maps;
+import android.support.annotation.NonNull;
+
+import com.mapswithme.maps.api.GeoFenceFeature;
+
public class LightFramework
{
public static native boolean nativeIsAuthenticated();
public static native int nativeGetNumberUnsentUGC();
+ public static native GeoFenceFeature[] nativeGetLocalAdsFeatures(double lat, double lon,
+ double radiusInMeters, int maxCount);
+ public static native void nativeLogLocalAdsEvent(int type, double lat, double lon, int accuracyInMeters,
+ long mwmVersion, @NonNull String countryId, int featureIndex);
}
diff --git a/android/src/com/mapswithme/maps/api/GeoFenceFeature.java b/android/src/com/mapswithme/maps/api/GeoFenceFeature.java
new file mode 100644
index 0000000000..b2670854a3
--- /dev/null
+++ b/android/src/com/mapswithme/maps/api/GeoFenceFeature.java
@@ -0,0 +1,23 @@
+package com.mapswithme.maps.api;
+
+/**
+ * Represents CampaignFeature from core.
+ */
+public class GeoFenceFeature
+{
+ public final long mwmVersion;
+ public final String countryId;
+ public final int featureIndex;
+ public final double latitude;
+ public final double longitude;
+
+ public GeoFenceFeature(long mwmVersion, String countryId, int featureIndex,
+ double latitude, double longitude)
+ {
+ this.mwmVersion = mwmVersion;
+ this.countryId = countryId;
+ this.featureIndex = featureIndex;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+}
diff --git a/android/src/com/mapswithme/maps/background/NotificationService.java b/android/src/com/mapswithme/maps/background/NotificationService.java
index 0ee96c6959..c6b9993906 100644
--- a/android/src/com/mapswithme/maps/background/NotificationService.java
+++ b/android/src/com/mapswithme/maps/background/NotificationService.java
@@ -44,16 +44,19 @@ public class NotificationService extends JobIntentService
private boolean notifyIsNotAuthenticated()
{
+ final boolean isAuthenticated = LightFramework.nativeIsAuthenticated();
+ final int numberUnsentUgc = LightFramework.nativeGetNumberUnsentUGC();
+
if (!PermissionsUtils.isExternalStorageGranted() ||
!NetworkPolicy.getCurrentNetworkUsageStatus() ||
- LightFramework.nativeIsAuthenticated() ||
- LightFramework.nativeGetNumberUnsentUGC() < MIN_COUNT_UNSENT_UGC)
+ isAuthenticated ||
+ numberUnsentUgc < MIN_COUNT_UNSENT_UGC)
{
LOGGER.d(TAG, "Authentication notification is rejected. External storage granted: " +
PermissionsUtils.isExternalStorageGranted() + ". Is user authenticated: " +
- LightFramework.nativeIsAuthenticated() + ". Current network usage status: " +
+ isAuthenticated + ". Current network usage status: " +
NetworkPolicy.getCurrentNetworkUsageStatus() + ". Number of unsent UGC: " +
- LightFramework.nativeGetNumberUnsentUGC());
+ numberUnsentUgc);
return false;
}
diff --git a/local_ads/event.hpp b/local_ads/event.hpp
index f45c7bb9a3..ef5c17bf06 100644
--- a/local_ads/event.hpp
+++ b/local_ads/event.hpp
@@ -13,7 +13,8 @@ enum class EventType
ShowPoint = 0,
OpenInfo,
ClickedPhone,
- ClickedWebsite
+ ClickedWebsite,
+ Visit
};
struct Event
diff --git a/local_ads/statistics.cpp b/local_ads/statistics.cpp
index d9face059d..2d65a3eefd 100644
--- a/local_ads/statistics.cpp
+++ b/local_ads/statistics.cpp
@@ -130,7 +130,7 @@ local_ads::Timestamp GetMaxTimestamp(std::list<local_ads::Event> const & events,
std::string GetPath(std::string const & fileName)
{
- return base::JoinFoldersToPath({GetPlatform().WritableDir(), kStatisticsFolderName}, fileName);
+ return base::JoinFoldersToPath({GetPlatform().SettingsDir(), kStatisticsFolderName}, fileName);
}
std::string GetPath(local_ads::Event const & event)
@@ -270,6 +270,12 @@ void Statistics::RegisterEvents(std::list<Event> && events)
std::bind(&Statistics::ProcessEvents, this, std::move(events)));
}
+void Statistics::RegisterEventSync(Event && event)
+{
+ std::list<Event> events = {std::move(event)};
+ ProcessEvents(events);
+}
+
void Statistics::SetEnabled(bool isEnabled)
{
m_isEnabled = isEnabled;
diff --git a/local_ads/statistics.hpp b/local_ads/statistics.hpp
index 2fc8dcc9c4..e71ac771e9 100644
--- a/local_ads/statistics.hpp
+++ b/local_ads/statistics.hpp
@@ -37,6 +37,8 @@ public:
void Startup();
void RegisterEvent(Event && event);
void RegisterEvents(std::list<Event> && events);
+ // Save the event synchronously (for lightweight frawework only).
+ void RegisterEventSync(Event && event);
void SetEnabled(bool isEnabled);
std::list<Event> WriteEventsForTesting(std::list<Event> const & events,
diff --git a/map/framework.cpp b/map/framework.cpp
index 355b9f3b8d..dd85e74c4c 100644
--- a/map/framework.cpp
+++ b/map/framework.cpp
@@ -229,6 +229,7 @@ void Framework::OnLocationUpdate(GpsInfo const & info)
#endif
m_routingManager.OnLocationUpdate(rInfo);
+ m_localAdsManager.OnLocationUpdate(rInfo, GetDrawScale());
}
void Framework::OnCompassUpdate(CompassInfo const & info)
diff --git a/map/framework_light.hpp b/map/framework_light.hpp
index fe0e1f25e1..89b4fa0af4 100644
--- a/map/framework_light.hpp
+++ b/map/framework_light.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "map/bookmark_manager.hpp"
+#include "map/local_ads_manager.hpp"
#include "map/user.hpp"
#include "ugc/storage.hpp"
@@ -28,6 +29,8 @@ enum RequestType
// and takes much time. For example it takes ~50ms on LG Nexus 5, ~100ms on Samsung A5, ~200ms on
// Fly IQ4403.
REQUEST_TYPE_LOCATION = 1u << 4,
+ REQUEST_TYPE_LOCAL_ADS_FEATURES = 1u << 5,
+ REQUEST_TYPE_LOCAL_ADS_STATISTICS = 1u << 6,
};
using RequestTypeMask = unsigned;
@@ -75,6 +78,18 @@ public:
request ^= REQUEST_TYPE_LOCATION;
}
+ if (request & REQUEST_TYPE_LOCAL_ADS_FEATURES)
+ {
+ m_localAdsFeaturesReader = std::make_unique<LocalAdsFeaturesReader>();
+ request ^= REQUEST_TYPE_LOCAL_ADS_FEATURES;
+ }
+
+ if (request & REQUEST_TYPE_LOCAL_ADS_STATISTICS)
+ {
+ m_localAdsStatistics = std::make_unique<Statistics>();
+ request ^= REQUEST_TYPE_LOCAL_ADS_STATISTICS;
+ }
+
CHECK_EQUAL(request, REQUEST_TYPE_EMPTY, ("Incorrect mask type:", request));
}
@@ -84,6 +99,12 @@ public:
template <RequestTypeMask Type>
auto Get(m2::PointD const & pt) const;
+ template <RequestTypeMask Type>
+ auto Get(double lat, double lon, double radiusInMeters, uint32_t maxCount);
+
+ template <RequestTypeMask Type>
+ auto Get();
+
private:
RequestTypeMask m_request;
bool m_userAuthStatus = false;
@@ -91,6 +112,8 @@ private:
size_t m_numberOfUnsentEdits = 0;
bool m_bookmarksCloudEnabled = false;
std::unique_ptr<CountryInfoReader> m_countryInfoReader;
+ std::unique_ptr<LocalAdsFeaturesReader> m_localAdsFeaturesReader;
+ std::unique_ptr<Statistics> m_localAdsStatistics;
};
template<>
@@ -130,4 +153,25 @@ auto Framework::Get<REQUEST_TYPE_LOCATION>(m2::PointD const & pt) const
return m_countryInfoReader->GetMwmInfo(pt);
}
+
+template <>
+auto Framework::Get<REQUEST_TYPE_LOCAL_ADS_FEATURES>(double lat, double lon,
+ double radiusInMeters, uint32_t maxCount)
+{
+ ASSERT(m_request & REQUEST_TYPE_LOCAL_ADS_FEATURES, (m_request));
+
+ CHECK(m_localAdsFeaturesReader, ());
+
+ return m_localAdsFeaturesReader->GetCampaignFeatures(lat, lon, radiusInMeters, maxCount);
+}
+
+template <>
+auto Framework::Get<REQUEST_TYPE_LOCAL_ADS_STATISTICS>()
+{
+ ASSERT(m_request & REQUEST_TYPE_LOCAL_ADS_STATISTICS, (m_request));
+
+ CHECK(m_localAdsStatistics, ());
+
+ return m_localAdsStatistics.get();
+}
} // namespace lightweight
diff --git a/map/local_ads_manager.cpp b/map/local_ads_manager.cpp
index 0ac3b9c06e..fb2f56e034 100644
--- a/map/local_ads_manager.cpp
+++ b/map/local_ads_manager.cpp
@@ -22,6 +22,7 @@
#include "coding/file_name_utils.hpp"
#include "coding/multilang_utf8_string.hpp"
+#include "coding/point_to_integer.hpp"
#include "coding/url_encode.hpp"
#include "base/url_helpers.hpp"
@@ -42,6 +43,7 @@ std::string const kServerUrl = LOCAL_ADS_SERVER_URL;
std::string const kCampaignPageUrl = LOCAL_ADS_COMPANY_PAGE_URL;
std::string const kCampaignFile = "local_ads_campaigns.dat";
+std::string const kCampaignFeaturesFile = "local_ads_features.dat";
std::string const kLocalAdsSymbolsFile = "local_ads_symbols.txt";
auto constexpr kWWanUpdateTimeout = std::chrono::hours(12);
uint8_t constexpr kRequestMinZoomLevel = 12;
@@ -50,6 +52,11 @@ auto constexpr kMaxDownloadingAttempts = 5;
auto constexpr kHiddenFeaturePriority = 1;
+double constexpr kMinCheckDistanceInMeters = 10.0;
+double constexpr kMinSearchRadiusInMeters = 20.0;
+uint32_t constexpr kMaxHitCount = 3;
+auto const kMinCheckInterval = std::chrono::minutes(1);
+
void SerializeCampaign(FileWriter & writer, std::string const & countryName,
LocalAdsManager::Timestamp const & ts,
std::vector<uint8_t> const & rawData)
@@ -69,7 +76,7 @@ void DeserializeCampaign(ReaderSource<FileReader> & src, std::string & countryNa
std::string GetPath(std::string const & fileName)
{
- return base::JoinFoldersToPath(GetPlatform().WritableDir(), fileName);
+ return base::JoinFoldersToPath(GetPlatform().SettingsDir(), fileName);
}
std::string MakeCampaignDownloadingURL(MwmSet::MwmId const & mwmId)
@@ -133,149 +140,97 @@ std::string GetCustomIcon(FeatureType & featureType)
return {};
}
-using CampaignData = std::map<FeatureID, std::shared_ptr<LocalAdsMarkData>>;
-
-CampaignData ParseCampaign(std::vector<uint8_t> const & rawData, MwmSet::MwmId const & mwmId,
- LocalAdsManager::Timestamp timestamp,
- LocalAdsManager::GetFeatureByIdFn const & getFeatureByIdFn)
+std::string MakeCampaignPageURL(FeatureID const & featureId)
{
- ASSERT(getFeatureByIdFn != nullptr, ());
- CampaignData data;
- auto campaigns = local_ads::Deserialize(rawData);
- for (local_ads::Campaign const & campaign : campaigns)
- {
- FeatureID featureId(mwmId, campaign.m_featureId);
- if (!featureId.IsValid())
- continue;
+ if (kCampaignPageUrl.empty() || !featureId.m_mwmId.IsAlive())
+ return {};
- if (campaign.m_priority == kHiddenFeaturePriority)
- {
- data.insert(std::make_pair(featureId, nullptr));
- continue;
- }
+ std::ostringstream ss;
+ ss << kCampaignPageUrl << "/" << featureId.m_mwmId.GetInfo()->GetVersion() << "/"
+ << UrlEncode(featureId.m_mwmId.GetInfo()->GetCountryName()) << "/" << featureId.m_index;
- std::string iconName = campaign.GetIconName();
- auto const expiration = timestamp + std::chrono::hours(24 * campaign.m_daysBeforeExpired);
- if (iconName.empty() || local_ads::Clock::now() > expiration)
+ url::Params params;
+ params.reserve(kMarketingParameters.size());
+ for (auto const & key : kMarketingParameters)
+ {
+ string value;
+ if (!marketing::Settings::Get(key, value))
continue;
- FeatureType featureType;
- if (getFeatureByIdFn(featureId, featureType))
- {
- auto const customIcon = GetCustomIcon(featureType);
- if (!customIcon.empty())
- iconName = customIcon;
- }
-
- auto markData = std::make_shared<LocalAdsMarkData>();
- markData->m_symbolName = iconName;
- markData->m_minZoomLevel = campaign.m_minZoomLevel;
- data.insert(std::make_pair(featureId, std::move(markData)));
+ params.push_back({key, value});
}
- return data;
+ return url::Make(ss.str(), params);
}
+} // namespace
-df::CustomFeatures ReadCampaignFeatures(LocalAdsManager::ReadFeaturesFn const & reader,
- CampaignData & campaignData)
+namespace features_cache
+{
+enum class Version : uint8_t
{
- ASSERT(reader != nullptr, ());
+ V0 = 0,
+ Latest = V0
+};
- std::vector<FeatureID> features;
- features.reserve(campaignData.size());
- df::CustomFeatures customFeatures;
- for (auto const & data : campaignData)
- {
- if (data.second)
- features.push_back(data.first);
- customFeatures.insert(std::make_pair(data.first, data.second != nullptr));
- }
+struct PackedCampaignFeature
+{
+ PackedCampaignFeature() = default;
+ PackedCampaignFeature(uint32_t featureIndex, int64_t mercator)
+ : m_featureIndex(featureIndex)
+ , m_mercator(mercator)
+ {}
- auto const deviceLang = StringUtf8Multilang::GetLangIndex(languages::GetCurrentNorm());
- reader(
- [&campaignData, deviceLang](FeatureType & ft) {
- auto it = campaignData.find(ft.GetID());
- CHECK(it != campaignData.end(), ());
- CHECK(it->second != nullptr, ());
- it->second->m_position = feature::GetCenter(ft, scales::GetUpperScale());
- ft.GetPreferredNames(true /* allowTranslit */, deviceLang, it->second->m_mainText,
- it->second->m_auxText);
- },
- features);
+ uint32_t m_featureIndex = 0;
+ int64_t m_mercator = 0;
+};
- return customFeatures;
+void SerializeVersion(FileWriter & writer, Version version)
+{
+ WriteToSink(writer, static_cast<uint8_t>(version));
}
-void CreateLocalAdsMarks(BookmarkManager * bmManager, CampaignData && campaignData)
+void SerializeMwmData(FileWriter & writer, std::string const & countryId, int64_t mwmVersion)
{
- if (bmManager == nullptr)
- return;
-
- // Here we copy campaign data, because we can create user marks only from UI thread.
- GetPlatform().RunTask(Platform::Thread::Gui, [bmManager, campaignData = std::move(campaignData)]()
- {
- auto editSession = bmManager->GetEditSession();
- for (auto const & data : campaignData)
- {
- if (data.second == nullptr)
- continue;
-
- auto * mark = editSession.CreateUserMark<LocalAdsMark>(data.second->m_position);
- mark->SetData(LocalAdsMarkData(*data.second));
- mark->SetFeatureId(data.first);
- }
- });
+ local_ads::WriteCountryName(writer, countryId);
+ local_ads::WriteZigZag(writer, mwmVersion);
}
-void DeleteLocalAdsMarks(BookmarkManager * bmManager, MwmSet::MwmId const & mwmId)
+void SerializePackedFeatures(FileWriter & writer, std::vector<PackedCampaignFeature> const & packedFeatures)
{
- if (bmManager == nullptr)
- return;
-
- GetPlatform().RunTask(Platform::Thread::Gui, [bmManager, mwmId]()
+ auto const featuresCount = static_cast<uint32_t>(packedFeatures.size());
+ WriteToSink(writer, featuresCount);
+ for (auto const & data : packedFeatures)
{
- bmManager->GetEditSession().DeleteUserMarks<LocalAdsMark>(UserMark::Type::LOCAL_ADS,
- [&mwmId](LocalAdsMark const * mark)
- {
- return mark->GetFeatureID().m_mwmId == mwmId;
- });
- });
+ WriteToSink(writer, data.m_featureIndex);
+ local_ads::WriteZigZag(writer, data.m_mercator);
+ }
}
-void DeleteAllLocalAdsMarks(BookmarkManager * bmManager)
+Version DeserializeVersion(ReaderSource<FileReader> & src)
{
- if (bmManager == nullptr)
- return;
-
- GetPlatform().RunTask(Platform::Thread::Gui, [bmManager]()
- {
- bmManager->GetEditSession().ClearGroup(UserMark::Type::LOCAL_ADS);
- });
+ return static_cast<Version>(ReadPrimitiveFromSource<uint8_t>(src));
}
-std::string MakeCampaignPageURL(FeatureID const & featureId)
+void DeserializeMwmData(ReaderSource<FileReader> & src, std::string & countryId, int64_t & mwmVersion)
{
- if (kCampaignPageUrl.empty() || !featureId.m_mwmId.IsAlive())
- return {};
-
- std::ostringstream ss;
- ss << kCampaignPageUrl << "/" << featureId.m_mwmId.GetInfo()->GetVersion() << "/"
- << UrlEncode(featureId.m_mwmId.GetInfo()->GetCountryName()) << "/" << featureId.m_index;
+ countryId = local_ads::ReadCountryName(src);
+ mwmVersion = local_ads::ReadZigZag(src);
+}
- url::Params params;
- params.reserve(kMarketingParameters.size());
- for (auto const & key : kMarketingParameters)
+void DeserializePackedFeatures(ReaderSource<FileReader> & src, std::vector<PackedCampaignFeature> & packedFeatures)
+{
+ auto const featuresCount = ReadPrimitiveFromSource<uint32_t>(src);
+ packedFeatures.clear();
+ packedFeatures.reserve(static_cast<size_t>(featuresCount));
+ for (uint32_t i = 0; i < featuresCount; ++i)
{
- string value;
- if (!marketing::Settings::Get(key, value))
- continue;
-
- params.push_back({key, value});
+ PackedCampaignFeature data;
+ data.m_featureIndex = ReadPrimitiveFromSource<uint32_t>(src);
+ data.m_mercator = local_ads::ReadZigZag(src);
+ packedFeatures.push_back(data);
}
-
- return url::Make(ss.str(), params);
}
-} // namespace
+} // namespace features_cache
LocalAdsManager::LocalAdsManager(GetMwmsByRectFn && getMwmsByRectFn,
GetMwmIdByNameFn && getMwmIdByName,
@@ -342,6 +297,11 @@ void LocalAdsManager::UpdateViewport(ScreenBase const & screen)
if (mwms.empty())
return;
+ if (m_lastMwms == mwms)
+ return;
+
+ m_lastMwms = mwms;
+
// Request local ads campaigns.
GetPlatform().RunTask(Platform::Thread::File, [this, mwms = std::move(mwms)]()
{
@@ -461,7 +421,6 @@ void LocalAdsManager::ProcessRequests(std::set<Request> && requests)
{
GetPlatform().RunTask(Platform::Thread::Network, [this, requests = std::move(requests)]
{
- std::string const campaignFile = GetPath(kCampaignFile);
for (auto const & request : requests)
{
auto const & mwm = request.first;
@@ -486,8 +445,8 @@ void LocalAdsManager::ProcessRequests(std::set<Request> && requests)
auto campaignData = ParseCampaign(info.m_data, mwm, info.m_created, m_getFeatureByIdFn);
if (!campaignData.empty())
{
- UpdateFeaturesCache(ReadCampaignFeatures(m_readFeaturesFn, campaignData));
- CreateLocalAdsMarks(m_bmManager, std::move(campaignData));
+ UpdateFeaturesCache(ReadCampaignFeatures(campaignData));
+ CreateLocalAdsMarks(std::move(campaignData));
}
}
@@ -503,17 +462,22 @@ void LocalAdsManager::ProcessRequests(std::set<Request> && requests)
ClearLocalAdsForMwm(mwm);
}
}
-
+
+ auto const campaignFile = GetPath(kCampaignFile);
+ auto const featureFile = GetPath(kCampaignFeaturesFile);
+
// Save data persistently.
- GetPlatform().RunTask(Platform::Thread::File, [this, campaignFile]
+ GetPlatform().RunTask(Platform::Thread::File, [this, campaignFile, featureFile]
{
WriteCampaignFile(campaignFile);
+ WriteFeaturesFile(featureFile);
});
});
}
void LocalAdsManager::OnDownloadCountry(std::string const & countryName)
{
+ m_lastMwms.clear();
GetPlatform().RunTask(Platform::Thread::File, [this, countryName]
{
std::lock_guard<std::mutex> lock(m_campaignsMutex);
@@ -524,6 +488,7 @@ void LocalAdsManager::OnDownloadCountry(std::string const & countryName)
void LocalAdsManager::OnMwmDeregistered(platform::LocalCountryFile const & countryFile)
{
+ m_lastMwms.clear();
MwmSet::MwmId mwmId;
{
std::lock_guard<std::mutex> lock(m_featuresCacheMutex);
@@ -587,11 +552,45 @@ void LocalAdsManager::WriteCampaignFile(std::string const & campaignFile)
}
}
+void LocalAdsManager::WriteFeaturesFile(std::string const & featuresFile)
+{
+ std::lock_guard<std::mutex> lock(m_featuresCacheMutex);
+ try
+ {
+ FileWriter writer(featuresFile);
+ features_cache::SerializeVersion(writer, features_cache::Version::V0);
+
+ MwmSet::MwmId lastMwm;
+ std::vector<features_cache::PackedCampaignFeature> packedData;
+ for (auto const & entry : m_featuresCache)
+ {
+ FeatureID const & fid = entry.first;
+ if (lastMwm != fid.m_mwmId && !packedData.empty())
+ {
+ features_cache::SerializeMwmData(writer, lastMwm.GetInfo()->GetCountryName(), lastMwm.GetInfo()->GetVersion());
+ features_cache::SerializePackedFeatures(writer, packedData);
+ packedData.clear();
+ }
+ lastMwm = fid.m_mwmId;
+ packedData.emplace_back(fid.m_index, PointToInt64Obsolete(entry.second.m_position, POINT_COORD_BITS));
+ }
+ if (!packedData.empty())
+ {
+ features_cache::SerializeMwmData(writer, lastMwm.GetInfo()->GetCountryName(), lastMwm.GetInfo()->GetVersion());
+ features_cache::SerializePackedFeatures(writer, packedData);
+ }
+ }
+ catch (RootException const & ex)
+ {
+ LOG(LWARNING, ("Error writing file:", featuresFile, ex.Msg()));
+ }
+}
+
void LocalAdsManager::Invalidate()
{
GetPlatform().RunTask(Platform::Thread::File, [this]
{
- DeleteAllLocalAdsMarks(m_bmManager);
+ DeleteAllLocalAdsMarks();
m_drapeEngine.SafeCall(&df::DrapeEngine::RemoveAllCustomFeatures);
CampaignData campaignData;
@@ -604,21 +603,142 @@ void LocalAdsManager::Invalidate()
campaignData.insert(data.begin(), data.end());
}
}
- UpdateFeaturesCache(ReadCampaignFeatures(m_readFeaturesFn, campaignData));
- CreateLocalAdsMarks(m_bmManager, std::move(campaignData));
+ UpdateFeaturesCache(ReadCampaignFeatures(campaignData));
+ CreateLocalAdsMarks(std::move(campaignData));
+ });
+}
+
+LocalAdsManager::CampaignData LocalAdsManager::ParseCampaign(std::vector<uint8_t> const & rawData,
+ MwmSet::MwmId const & mwmId,
+ Timestamp timestamp,
+ GetFeatureByIdFn const & getFeatureByIdFn) const
+{
+ ASSERT(getFeatureByIdFn != nullptr, ());
+ CampaignData data;
+ auto campaigns = local_ads::Deserialize(rawData);
+ for (local_ads::Campaign const & campaign : campaigns)
+ {
+ FeatureID featureId(mwmId, campaign.m_featureId);
+ if (!featureId.IsValid())
+ continue;
+
+ if (campaign.m_priority == kHiddenFeaturePriority)
+ {
+ data.insert(std::make_pair(featureId, nullptr));
+ continue;
+ }
+
+ std::string iconName = campaign.GetIconName();
+ auto const expiration = timestamp + std::chrono::hours(24 * campaign.m_daysBeforeExpired);
+ if (iconName.empty() || local_ads::Clock::now() > expiration)
+ continue;
+
+ FeatureType featureType;
+ if (getFeatureByIdFn(featureId, featureType))
+ {
+ auto const customIcon = GetCustomIcon(featureType);
+ if (!customIcon.empty())
+ iconName = customIcon;
+ }
+
+ auto markData = std::make_shared<LocalAdsMarkData>();
+ markData->m_symbolName = iconName;
+ markData->m_minZoomLevel = campaign.m_minZoomLevel;
+ data.insert(std::make_pair(featureId, std::move(markData)));
+ }
+
+ return data;
+}
+
+void LocalAdsManager::CreateLocalAdsMarks(CampaignData && campaignData)
+{
+ if (m_bmManager == nullptr)
+ return;
+
+ // Here we copy campaign data, because we can create user marks only from UI thread.
+ GetPlatform().RunTask(Platform::Thread::Gui, [this, campaignData = std::move(campaignData)]()
+ {
+ auto editSession = m_bmManager->GetEditSession();
+ for (auto const & data : campaignData)
+ {
+ if (data.second == nullptr)
+ continue;
+
+ auto * mark = editSession.CreateUserMark<LocalAdsMark>(data.second->m_position);
+ mark->SetData(LocalAdsMarkData(*data.second));
+ mark->SetFeatureId(data.first);
+ }
});
}
-void LocalAdsManager::UpdateFeaturesCache(df::CustomFeatures && features)
+void LocalAdsManager::DeleteLocalAdsMarks(MwmSet::MwmId const & mwmId)
+{
+ if (m_bmManager == nullptr)
+ return;
+
+ GetPlatform().RunTask(Platform::Thread::Gui, [this, mwmId]()
+ {
+ m_bmManager->GetEditSession().DeleteUserMarks<LocalAdsMark>(UserMark::Type::LOCAL_ADS,
+ [&mwmId](LocalAdsMark const * mark)
+ {
+ return mark->GetFeatureID().m_mwmId == mwmId;
+ });
+ });
+}
+
+void LocalAdsManager::DeleteAllLocalAdsMarks()
+{
+ if (m_bmManager == nullptr)
+ return;
+
+ GetPlatform().RunTask(Platform::Thread::Gui, [this]()
+ {
+ m_bmManager->GetEditSession().ClearGroup(UserMark::Type::LOCAL_ADS);
+ });
+}
+
+LocalAdsManager::FeaturesCache LocalAdsManager::ReadCampaignFeatures(CampaignData & campaignData) const
+{
+ ASSERT(m_readFeaturesFn != nullptr, ());
+
+ LocalAdsManager::FeaturesCache cache;
+
+ std::vector<FeatureID> features;
+ features.reserve(campaignData.size());
+ for (auto const & data : campaignData)
+ features.push_back(data.first);
+
+ auto const deviceLang = StringUtf8Multilang::GetLangIndex(languages::GetCurrentNorm());
+ m_readFeaturesFn(
+ [&campaignData, &cache, deviceLang](FeatureType & ft) {
+ auto it = campaignData.find(ft.GetID());
+ CHECK(it != campaignData.end(), ());
+
+ LocalAdsManager::CacheEntry entry;
+ entry.m_position = feature::GetCenter(ft, scales::GetUpperScale());
+ entry.m_isCustom = it->second != nullptr;
+ if (it->second != nullptr)
+ {
+ it->second->m_position = entry.m_position;
+ ft.GetPreferredNames(true /* allowTranslit */, deviceLang, it->second->m_mainText, it->second->m_auxText);
+ }
+ cache.insert(std::make_pair(it->first, entry));
+ },
+ features);
+ return cache;
+}
+
+void LocalAdsManager::UpdateFeaturesCache(FeaturesCache && features)
{
- df::CustomFeatures featuresCache;
+ df::CustomFeatures customFeatures;
{
std::lock_guard<std::mutex> lock(m_featuresCacheMutex);
if (!features.empty())
m_featuresCache.insert(features.begin(), features.end());
- featuresCache = m_featuresCache;
+ for (auto const & entry : m_featuresCache)
+ customFeatures.insert(std::make_pair(entry.first, entry.second.m_isCustom));
}
- m_drapeEngine.SafeCall(&df::DrapeEngine::SetCustomFeatures, std::move(featuresCache));
+ m_drapeEngine.SafeCall(&df::DrapeEngine::SetCustomFeatures, std::move(customFeatures));
}
void LocalAdsManager::ClearLocalAdsForMwm(MwmSet::MwmId const & mwmId)
@@ -639,7 +759,7 @@ void LocalAdsManager::ClearLocalAdsForMwm(MwmSet::MwmId const & mwmId)
m_drapeEngine.SafeCall(&df::DrapeEngine::RemoveCustomFeatures, mwmId);
// Delete marks.
- DeleteLocalAdsMarks(m_bmManager, mwmId);
+ DeleteLocalAdsMarks(mwmId);
}
bool LocalAdsManager::Contains(FeatureID const & featureId) const
@@ -668,6 +788,7 @@ void LocalAdsManager::OnSubscriptionChanged(SubscriptionType type, bool isActive
return;
m_isEnabled = enabled;
+ m_lastMwms.clear();
if (enabled)
{
if (!m_isStarted)
@@ -695,7 +816,7 @@ void LocalAdsManager::OnSubscriptionChanged(SubscriptionType type, bool isActive
}
// Clear all graphics.
- DeleteAllLocalAdsMarks(m_bmManager);
+ DeleteAllLocalAdsMarks();
m_drapeEngine.SafeCall(&df::DrapeEngine::RemoveAllCustomFeatures);
});
@@ -704,8 +825,147 @@ void LocalAdsManager::OnSubscriptionChanged(SubscriptionType type, bool isActive
}
}
+void LocalAdsManager::OnLocationUpdate(location::GpsInfo const & info, int zoomLevel)
+{
+ if (!m_isEnabled)
+ return;
+
+ if (std::chrono::steady_clock::now() < (m_lastCheckTime + kMinCheckInterval))
+ return;
+
+ auto const pt = MercatorBounds::FromLatLon(info.m_latitude, info.m_longitude);
+
+ if ((m_foundFeatureHitCount == 0 || m_foundFeatureHitCount == kMaxHitCount) &&
+ MercatorBounds::DistanceOnEarth(m_lastCheckedPos, pt) < kMinCheckDistanceInMeters)
+ {
+ return;
+ }
+
+ auto const radius = std::max(info.m_horizontalAccuracy, kMinSearchRadiusInMeters);
+ auto searchRect = MercatorBounds::RectByCenterXYAndSizeInMeters(pt, radius);
+
+ FeatureID fid;
+ {
+ std::lock_guard<std::mutex> lock(m_featuresCacheMutex);
+ double minDist = numeric_limits<double>::max();
+ for (auto const & pair : m_featuresCache)
+ {
+ auto const & pos = pair.second.m_position;
+ if (!searchRect.IsPointInside(pos))
+ continue;
+ auto const dist = MercatorBounds::DistanceOnEarth(pos, pt);
+ if (dist < radius && dist < minDist)
+ {
+ minDist = dist;
+ fid = pair.first;
+ }
+ }
+ }
+
+ m_lastCheckTime = std::chrono::steady_clock::now();
+ m_lastCheckedPos = pt;
+
+ if (fid.IsValid())
+ {
+ m_statistics.RegisterEvent(local_ads::Event(local_ads::EventType::Visit, fid.m_mwmId.GetInfo()->GetVersion(),
+ fid.m_mwmId.GetInfo()->GetCountryName(), fid.m_index,
+ static_cast<uint8_t>(zoomLevel), local_ads::Clock::now(),
+ info.m_latitude, info.m_longitude,
+ static_cast<uint16_t>(info.m_horizontalAccuracy)));
+ if (m_lastFoundFeature != fid)
+ m_foundFeatureHitCount = 1;
+ else if (m_foundFeatureHitCount < kMaxHitCount)
+ ++m_foundFeatureHitCount;
+ }
+ else
+ {
+ m_foundFeatureHitCount = 0;
+ }
+ m_lastFoundFeature = fid;
+}
+
bool LocalAdsManager::BackoffStats::CanRetry() const
{
return !m_fileIsAbsent && m_attemptsCount < kMaxDownloadingAttempts &&
std::chrono::steady_clock::now() > (m_lastDownloading + m_currentTimeout);
}
+
+namespace lightweight
+{
+void LocalAdsFeaturesReader::ReadCampaignFeaturesFile()
+{
+ m_features.clear();
+ std::string const featuresFile = GetPath(kCampaignFeaturesFile);
+ try
+ {
+ FileReader reader(featuresFile);
+ ReaderSource<FileReader> src(reader);
+
+ auto const version = features_cache::DeserializeVersion(src);
+ if (version != features_cache::Version::V0)
+ return;
+
+ std::string countryId;
+ int64_t mwmVersion;
+ std::vector<features_cache::PackedCampaignFeature> packedData;
+ while (src.Size() > 0)
+ {
+ features_cache::DeserializeMwmData(src, countryId, mwmVersion);
+ features_cache::DeserializePackedFeatures(src, packedData);
+
+ for (auto const & data : packedData)
+ {
+ auto const pos = Int64ToPointObsolete(data.m_mercator, POINT_COORD_BITS);
+ m_features.push_back(CampaignFeature(mwmVersion, countryId, data.m_featureIndex,
+ MercatorBounds::YToLat(pos.y), MercatorBounds::XToLon(pos.x)));
+ }
+ }
+ }
+ catch (Reader::Exception const & ex)
+ {
+ LOG(LWARNING, ("Error reading file:", featuresFile, ex.Msg()));
+ FileWriter::DeleteFileX(featuresFile);
+ }
+}
+
+std::vector<CampaignFeature> LocalAdsFeaturesReader::GetCampaignFeatures(double lat, double lon, double radiusInMeters,
+ uint32_t maxCount)
+{
+ if (!m_initialized)
+ {
+ ReadCampaignFeaturesFile();
+ m_initialized = true;
+ }
+
+ if (m_features.empty())
+ return {};
+
+ auto const pt = MercatorBounds::FromLatLon(lat, lon);
+ auto searchRect = MercatorBounds::RectByCenterXYAndSizeInMeters(pt, radiusInMeters);
+
+ std::multimap<uint32_t, CampaignFeature> sortedFeatures;
+ for (auto const & f : m_features)
+ {
+ auto const pos = MercatorBounds::FromLatLon(f.m_lat, f.m_lon);
+ if (!searchRect.IsPointInside(pos))
+ continue;
+ auto const dist = static_cast<uint32_t>(MercatorBounds::DistanceOnEarth(pos, pt));
+ sortedFeatures.insert(std::make_pair(dist, f));
+ }
+
+ std::vector<CampaignFeature> filteredFeatures;
+ filteredFeatures.reserve(std::min(sortedFeatures.size(), static_cast<size_t>(maxCount)));
+ for (auto const & pair : sortedFeatures)
+ {
+ filteredFeatures.push_back(pair.second);
+ if (filteredFeatures.size() == maxCount)
+ break;
+ }
+ return filteredFeatures;
+}
+
+void Statistics::RegisterEvent(local_ads::Event && event)
+{
+ m_statistics.RegisterEventSync(std::move(event));
+}
+} // namespace lightweight
diff --git a/map/local_ads_manager.hpp b/map/local_ads_manager.hpp
index 0ecb1c5700..7d6b6aee37 100644
--- a/map/local_ads_manager.hpp
+++ b/map/local_ads_manager.hpp
@@ -16,6 +16,8 @@
#include "indexer/ftypes_mapping.hpp"
#include "indexer/mwm_set.hpp"
+#include "platform/location.hpp"
+
#include "base/thread.hpp"
#include <atomic>
@@ -33,6 +35,21 @@ class TypesHolder;
}
class BookmarkManager;
+struct LocalAdsMarkData;
+
+struct CampaignFeature
+{
+ CampaignFeature() = default;
+ CampaignFeature(int64_t const & mwmVersion, std::string const & countryId, uint32_t featureIndex,
+ double lat, double lon)
+ : m_mwmVersion(mwmVersion), m_countryId(countryId), m_featureIndex(featureIndex), m_lat(lat), m_lon(lon) {}
+
+ int64_t m_mwmVersion = FeatureID::kInvalidMwmVersion;
+ std::string m_countryId;
+ uint32_t m_featureIndex = 0;
+ double m_lat = 0.0;
+ double m_lon = 0.0;
+};
class LocalAdsManager : public SubscriptionListener
{
@@ -43,6 +60,7 @@ public:
using ReadFeaturesFn = std::function<void(ReadFeatureTypeFn const &,
std::vector<FeatureID> const & features)>;
using GetFeatureByIdFn = std::function<bool(FeatureID const &, FeatureType &)>;
+ using OnCampaignFeaturesFn = std::function<void(std::vector<FeatureID> const & features)>;
using Timestamp = local_ads::Timestamp;
LocalAdsManager(GetMwmsByRectFn && getMwmsByRectFn, GetMwmIdByNameFn && getMwmIdByName,
@@ -67,6 +85,7 @@ public:
std::string GetCompanyUrl(FeatureID const & featureId) const;
void OnSubscriptionChanged(SubscriptionType type, bool isActive) override;
+ void OnLocationUpdate(location::GpsInfo const & info, int zoomLevel);
private:
enum class RequestType
@@ -76,6 +95,13 @@ private:
};
using Request = std::pair<MwmSet::MwmId, RequestType>;
+ struct CacheEntry
+ {
+ bool m_isCustom = false;
+ m2::PointD m_position;
+ };
+ using FeaturesCache = std::map<FeatureID, CacheEntry>;
+
void Start();
void ProcessRequests(std::set<Request> && campaignMwms);
@@ -83,7 +109,18 @@ private:
void ReadCampaignFile(std::string const & campaignFile);
void WriteCampaignFile(std::string const & campaignFile);
- void UpdateFeaturesCache(df::CustomFeatures && features);
+ void WriteFeaturesFile(std::string const & featuresFile);
+
+ using CampaignData = std::map<FeatureID, std::shared_ptr<LocalAdsMarkData>>;
+ CampaignData ParseCampaign(std::vector<uint8_t> const & rawData, MwmSet::MwmId const & mwmId,
+ Timestamp timestamp, GetFeatureByIdFn const & getFeatureByIdFn) const;
+ FeaturesCache ReadCampaignFeatures(CampaignData & campaignData) const;
+
+ void CreateLocalAdsMarks(CampaignData && campaignData);
+ void DeleteLocalAdsMarks(MwmSet::MwmId const & mwmId);
+ void DeleteAllLocalAdsMarks();
+
+ void UpdateFeaturesCache(FeaturesCache && features);
void ClearLocalAdsForMwm(MwmSet::MwmId const &mwmId);
void FillSupportedTypes();
@@ -101,7 +138,7 @@ private:
std::atomic<bool> m_isStarted;
- std::atomic<BookmarkManager *> m_bmManager;
+ BookmarkManager * m_bmManager;
df::DrapeEngineSafePtr m_drapeEngine;
@@ -114,7 +151,7 @@ private:
std::map<std::string, CampaignInfo> m_info;
std::mutex m_campaignsMutex;
- df::CustomFeatures m_featuresCache;
+ FeaturesCache m_featuresCache;
mutable std::mutex m_featuresCacheMutex;
ftypes::HashSetMatcher<uint32_t> m_supportedTypes;
@@ -140,7 +177,37 @@ private:
};
std::map<MwmSet::MwmId, BackoffStats> m_failedDownloads;
std::set<MwmSet::MwmId> m_downloadingMwms;
-
+ std::vector<MwmSet::MwmId> m_lastMwms;
+
local_ads::Statistics m_statistics;
std::atomic<bool> m_isEnabled;
+
+ m2::PointD m_lastCheckedPos;
+ std::chrono::steady_clock::time_point m_lastCheckTime;
+ FeatureID m_lastFoundFeature;
+ uint32_t m_foundFeatureHitCount = 0;
+};
+
+namespace lightweight
+{
+class LocalAdsFeaturesReader
+{
+public:
+ std::vector<CampaignFeature> GetCampaignFeatures(double lat, double lon,
+ double radiusInMeters, uint32_t maxCount);
+private:
+ void ReadCampaignFeaturesFile();
+
+ bool m_initialized = false;
+ std::vector<CampaignFeature> m_features;
+};
+
+class Statistics
+{
+public:
+ void RegisterEvent(local_ads::Event && event);
+
+private:
+ local_ads::Statistics m_statistics;
};
+} // namespace lightweight