diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9c3b704346..5a11f9dcb4 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -87,6 +87,7 @@ LOCAL_SRC_FILES := \ com/mapswithme/maps/editor/Editor.cpp \ com/mapswithme/maps/editor/OsmOAuth.cpp \ com/mapswithme/maps/Framework.cpp \ + com/mapswithme/maps/locals/Locals.cpp \ com/mapswithme/maps/LocationState.cpp \ com/mapswithme/maps/LocationHelper.cpp \ com/mapswithme/maps/MapFragment.cpp \ diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 949eed4c19..ee4b435834 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -614,6 +614,20 @@ int Framework::ToDoAfterUpdate() const return (int) m_work.ToDoAfterUpdate(); } +uint64_t Framework::GetLocals(JNIEnv * env, jobject policy, double lat, double lon, + locals::LocalsSuccessCallback const & successFn, + locals::LocalsErrorCallback const & errorFn) +{ + auto api = NativeFramework()->GetLocalsApi(ToNativeNetworkPolicy(env, policy)); + if (api == nullptr) + return 0; + + std::string const langStr = languages::GetCurrentNorm(); + size_t constexpr kResultsOnPage = 5; + size_t constexpr kPageNumber = 1; + return api->GetLocals(lat, lon, langStr, kResultsOnPage, kPageNumber, successFn, errorFn); +} + void Framework::LogLocalAdsEvent(local_ads::EventType type, double lat, double lon, uint16_t accuracy) { auto const & info = g_framework->GetPlacePageInfo(); diff --git a/android/jni/com/mapswithme/maps/Framework.hpp b/android/jni/com/mapswithme/maps/Framework.hpp index 96b1edd510..f4cd96dc0b 100644 --- a/android/jni/com/mapswithme/maps/Framework.hpp +++ b/android/jni/com/mapswithme/maps/Framework.hpp @@ -16,6 +16,8 @@ #include "local_ads/event.hpp" +#include "partners_api/locals_api.hpp" + #include "platform/country_defines.hpp" #include "platform/location.hpp" @@ -210,6 +212,10 @@ namespace android int ToDoAfterUpdate() const; + uint64_t GetLocals(JNIEnv * env, jobject policy, double lat, double lon, + locals::LocalsSuccessCallback const & successFn, + locals::LocalsErrorCallback const & errorFn); + void LogLocalAdsEvent(local_ads::EventType event, double lat, double lon, uint16_t accuracy); }; } diff --git a/android/jni/com/mapswithme/maps/locals/Locals.cpp b/android/jni/com/mapswithme/maps/locals/Locals.cpp index 44385223ff..e99f5aa3c0 100644 --- a/android/jni/com/mapswithme/maps/locals/Locals.cpp +++ b/android/jni/com/mapswithme/maps/locals/Locals.cpp @@ -1,32 +1,116 @@ +#include "android/jni/com/mapswithme/core/jni_helper.hpp" #include "android/jni/com/mapswithme/maps/Framework.hpp" -#include "android/jni/com/mapswithme/core/jni_helper.hpp" -#include "partners_api/locals_api.hpp" +#include +#include namespace { +jclass g_localsClass = nullptr; +jobject g_localsInstance; +jmethodID g_onLocalsReceivedMethod; +jmethodID g_onLocalsErrorReceivedMethod; jclass g_localExpertClass; jmethodID g_localExpertConstructor; +jclass g_localErrorClass; +jmethodID g_localErrorConstructor; +uint64_t g_lastRequestId = 0; void PrepareClassRefs(JNIEnv * env) { - if (g_localExpertClass) + if (g_localsClass != nullptr) return; -@NonNull int id, -@NotNull String name, -@NotNull String country, -@NotNull String city, -double rating, int reviewCount, @NonNull double price - @NonNull String currency, String motto, String about, - @NonNull String offer, @NonNull String pageUrl, @NonNull String photoUrl + g_localsClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/locals/Locals"); + static jfieldID const localsInstanceField = jni::GetStaticFieldID(env, g_localsClass, "INSTANCE", + "Lcom/mapswithme/maps/locals/Locals;"); + g_localsInstance = env->GetStaticObjectField(g_localsClass, localsInstanceField); + g_onLocalsReceivedMethod = jni::GetMethodID(env, g_localsInstance, "onLocalsReceived", + "([Lcom/mapswithme/maps/locals/LocalExpert;)V"); + g_onLocalsErrorReceivedMethod = jni::GetMethodID(env, g_localsInstance, + "onLocalsErrorReceived", + "(Lcom/mapswithme/maps/locals/LocalsError;)V"); + // int id, @NonNull String name, @NonNull String country, + // @NonNull String city, double rating, int reviewCount, + // double price, @NonNull String currency, @NonNull String motto, + // @NonNull String about, @NonNull String offer, @NonNull String pageUrl, + // @NonNull String photoUrl g_localExpertClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/locals/LocalExpert"); g_localExpertConstructor = - jni::GetConstructorID(env, g_viatorProductClass, + jni::GetConstructorID(env, g_localExpertClass, "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;DID" "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + // @ErrorCode int code, @NonNull String message + g_localErrorClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/locals/LocalError"); + g_localErrorConstructor = jni::GetConstructorID(env, g_localErrorClass, + "(ILjava/lang/String;)V"); } +void OnLocalsSuccess(uint64_t requestId, std::vector const & locals, + size_t pageNumber, size_t countPerPage, bool hasPreviousPage, + bool hasNextPage) +{ + if (g_lastRequestId != requestId) + return; + + JNIEnv * env = jni::GetEnv(); + + auto const localExpertBuilder = [](JNIEnv * env, locals::LocalExpert const & expert) + { + jni::TScopedLocalRef jName(env, jni::ToJavaString(env, expert.m_name)); + jni::TScopedLocalRef jCountry(env, jni::ToJavaString(env, expert.m_country)); + jni::TScopedLocalRef jCity(env, jni::ToJavaString(env, expert.m_city)); + jni::TScopedLocalRef jCurrency(env, jni::ToJavaString(env, expert.m_currency)); + jni::TScopedLocalRef jMotto(env, jni::ToJavaString(env, expert.m_motto)); + jni::TScopedLocalRef jAboutExpert(env, jni::ToJavaString(env, expert.m_aboutExpert)); + jni::TScopedLocalRef jOfferDescription(env, jni::ToJavaString(env, expert.m_offerDescription)); + jni::TScopedLocalRef jPageUrl(env, jni::ToJavaString(env, expert.m_pageUrl)); + jni::TScopedLocalRef jPhotoUrl(env, jni::ToJavaString(env, expert.m_photoUrl)); + + return env->NewObject(g_localExpertClass, g_localExpertConstructor, + expert.m_id, jName.get(), jCountry.get(), jCity.get(), + expert.m_rating, expert.m_reviewCount, expert.m_pricePerHour, + jCurrency.get(), jMotto.get(), jAboutExpert.get(), + jOfferDescription.get(), jPageUrl.get(), jPhotoUrl.get()); + }; + + jni::TScopedLocalObjectArrayRef jLocals(env, jni::ToJavaArray(env, g_localExpertClass, locals, + localExpertBuilder)); + + env->CallVoidMethod(g_localsInstance, g_onLocalsReceivedMethod, jLocals.get()); + + jni::HandleJavaException(env); +} + +void OnLocalsError(uint64_t requestId, int errorCode, std::string const & errorMessage) +{ + if (g_lastRequestId != requestId) + return; + + JNIEnv * env = jni::GetEnv(); + + jni::TScopedLocalRef errorStr(env, jni::ToJavaString(env, errorMessage)); + jni::TScopedLocalRef errorObj(env, env->NewObject(g_localErrorClass, g_localErrorConstructor, + errorCode, errorStr.get())); + + env->CallVoidMethod(g_localsInstance, g_onLocalsErrorReceivedMethod, errorObj.get()); + + jni::HandleJavaException(env); +} } // namespace + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_mapswithme_maps_locals_Locals_nativeRequestLocals(JNIEnv * env, jclass clazz, + jobject policy, jdouble lat, + jdouble lon) +{ + PrepareClassRefs(env); + g_lastRequestId = g_framework->GetLocals(env, policy, lat, lon, &OnLocalsSuccess, + &OnLocalsError); +} +} // extern "C" diff --git a/android/src/com/mapswithme/maps/locals/LocalExpert.java b/android/src/com/mapswithme/maps/locals/LocalExpert.java index bf2eed64f4..964d095352 100644 --- a/android/src/com/mapswithme/maps/locals/LocalExpert.java +++ b/android/src/com/mapswithme/maps/locals/LocalExpert.java @@ -5,7 +5,7 @@ import android.os.Parcelable; import android.support.annotation.NonNull; -public final class LocalExpert implements Parcelable +final class LocalExpert implements Parcelable { private final int mId; @NonNull @@ -104,10 +104,7 @@ public final class LocalExpert implements Parcelable } @Override - public int describeContents() - { - return 0; - } + public int describeContents() { return 0; } int getId() { return mId; } @@ -122,15 +119,9 @@ public final class LocalExpert implements Parcelable double getRating() { return mRating; } - int getReviewCount() - { - return mReviewCount; - } + int getReviewCount() { return mReviewCount; } - double getPrice() - { - return mPrice; - } + double getPrice() { return mPrice; } @NonNull String getCurrency() { return mCurrency; } @@ -145,10 +136,7 @@ public final class LocalExpert implements Parcelable String getOfferDescription() { return mOfferDescription; } @NonNull - String getPageUrl() - { - return mPageUrl; - } + String getPageUrl() { return mPageUrl;} @NonNull String getPhotoUrl() { return mPhotoUrl; } @@ -162,17 +150,40 @@ public final class LocalExpert implements Parcelable LocalExpert that = (LocalExpert) o; if (mId != that.mId) return false; - if (!mName.equals(that.mName)) return false; - if (!mCountry.equals(that.mCountry)) return false; - if (!mCity.equals(that.mCity)) return false; if (Double.compare(that.mRating, mRating) != 0) return false; if (mReviewCount != that.mReviewCount) return false; if (Double.compare(that.mPrice, mPrice) != 0) return false; + if (!mName.equals(that.mName)) return false; + if (!mCountry.equals(that.mCountry)) return false; + if (!mCity.equals(that.mCity)) return false; if (!mCurrency.equals(that.mCurrency)) return false; if (!mMotto.equals(that.mMotto)) return false; if (!mAboutExpert.equals(that.mAboutExpert)) return false; if (!mOfferDescription.equals(that.mOfferDescription)) return false; - if (!mPhotoUrl.equals(that.mPhotoUrl)) return false; - return mPageUrl.equals(that.mPageUrl); + if (!mPageUrl.equals(that.mPageUrl)) return false; + return mPhotoUrl.equals(that.mPhotoUrl); + } + + @Override + public int hashCode() + { + int result; + long temp; + result = mId; + result = 31 * result + mName.hashCode(); + result = 31 * result + mCountry.hashCode(); + result = 31 * result + mCity.hashCode(); + temp = Double.doubleToLongBits(mRating); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + mReviewCount; + temp = Double.doubleToLongBits(mPrice); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + mCurrency.hashCode(); + result = 31 * result + mMotto.hashCode(); + result = 31 * result + mAboutExpert.hashCode(); + result = 31 * result + mOfferDescription.hashCode(); + result = 31 * result + mPageUrl.hashCode(); + result = 31 * result + mPhotoUrl.hashCode(); + return result; } } diff --git a/android/src/com/mapswithme/maps/locals/Locals.java b/android/src/com/mapswithme/maps/locals/Locals.java new file mode 100644 index 0000000000..f14075e4da --- /dev/null +++ b/android/src/com/mapswithme/maps/locals/Locals.java @@ -0,0 +1,54 @@ +package com.mapswithme.maps.locals; + +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mapswithme.util.NetworkPolicy; +import com.mapswithme.util.concurrency.UiThread; + +final class Locals +{ + public static final Locals INSTANCE = new Locals(); + + @Nullable + private LocalsListener mListener; + + private Locals() {} + + public void setLocalsListener(@Nullable LocalsListener listener) + { + mListener = listener; + } + + public native void nativeRequestLocals(@NonNull NetworkPolicy policy, + double lat, double lon); + + // Called from JNI. + @MainThread + void onLocalsReceived(@NonNull LocalExpert[] experts) + { + if (!UiThread.currentThreadIsUi()) + throw new AssertionError("Must be called from UI thread!"); + + if (mListener != null) + mListener.onLocalsReceived(experts); + } + + // Called from JNI. + @MainThread + void onLocalsErrorReceived(@NonNull LocalsError error) + { + if (!UiThread.currentThreadIsUi()) + throw new AssertionError("Must be called from UI thread!"); + + if (mListener != null) + mListener.onLocalsErrorReceived(error); + } + + public interface LocalsListener + { + void onLocalsReceived(@NonNull LocalExpert[] experts); + void onLocalsErrorReceived(@NonNull LocalsError error); + } +} diff --git a/android/src/com/mapswithme/maps/locals/LocalsError.java b/android/src/com/mapswithme/maps/locals/LocalsError.java new file mode 100644 index 0000000000..0ac4f08d24 --- /dev/null +++ b/android/src/com/mapswithme/maps/locals/LocalsError.java @@ -0,0 +1,48 @@ +package com.mapswithme.maps.locals; + +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +class LocalsError +{ + static final int UNKNOWN_ERROR = 0; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ UNKNOWN_ERROR }) + @interface ErrorCode {} + + @ErrorCode + private final int mCode; + @NonNull + private final String mMessage; + + public LocalsError(@ErrorCode int code, @NonNull String message) + { + mCode = code; + mMessage = message; + } + + @ErrorCode + public int getCode() + { + return mCode; + } + + @NonNull + public String getMessage() + { + return mMessage; + } + + @Override + public String toString() + { + return "LocalsError{" + + "mCode=" + mCode + + ", mMessage=" + mMessage + + '}'; + } +}