From 219b3c78f54e08e9abec9655916b9d5252ff8678 Mon Sep 17 00:00:00 2001 From: Arsentiy Milchakov Date: Tue, 6 Feb 2018 19:04:33 +0300 Subject: [PATCH] [booking][ios][android] deep links into platforms --- android/jni/com/mapswithme/maps/Sponsored.cpp | 3 +- .../com/mapswithme/maps/taxi/TaxiManager.cpp | 2 +- .../routing/RoutingBottomMenuController.java | 38 +++++++--- .../com/mapswithme/maps/taxi/TaxiManager.java | 25 +++++-- .../maps/widget/placepage/PlacePageView.java | 16 ++++- .../maps/widget/placepage/Sponsored.java | 28 +++++++- .../SponsoredLinks.java} | 6 +- android/src/com/mapswithme/util/Utils.java | 70 +++++++++---------- iphone/Maps/MAPSME.plist | 1 + iphone/Maps/UI/PlacePage/MWMPlacePageData.h | 1 + iphone/Maps/UI/PlacePage/MWMPlacePageData.mm | 18 ++++- .../Maps/UI/PlacePage/MWMPlacePageManager.mm | 6 +- 12 files changed, 149 insertions(+), 65 deletions(-) rename android/src/com/mapswithme/{maps/taxi/TaxiLinks.java => util/SponsoredLinks.java} (72%) diff --git a/android/jni/com/mapswithme/maps/Sponsored.cpp b/android/jni/com/mapswithme/maps/Sponsored.cpp index fcf62da9a7..c2ff6f1e86 100644 --- a/android/jni/com/mapswithme/maps/Sponsored.cpp +++ b/android/jni/com/mapswithme/maps/Sponsored.cpp @@ -61,7 +61,7 @@ void PrepareClassRefs(JNIEnv * env, jclass sponsoredClass) // Sponsored(String rating, String price, String urlBook, String urlDescription) g_sponsoredClassConstructor = jni::GetConstructorID( env, g_sponsoredClass, - "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;" + "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;IILjava/lang/String;)V"); // static void onPriceReceived(final String id, final String price, final String currency) g_priceCallback = @@ -124,6 +124,7 @@ JNIEXPORT jobject JNICALL Java_com_mapswithme_maps_widget_placepage_Sponsored_na static_cast(place_page::rating::GetImpress(ppInfo.GetRatingRawValue())), jni::ToJavaString(env, ppInfo.GetApproximatePricing()), jni::ToJavaString(env, ppInfo.GetSponsoredUrl()), + jni::ToJavaString(env, ppInfo.GetSponsoredDeepLink()), jni::ToJavaString(env, ppInfo.GetSponsoredDescriptionUrl()), jni::ToJavaString(env, ppInfo.GetSponsoredReviewUrl()), static_cast(ppInfo.GetSponsoredType()), diff --git a/android/jni/com/mapswithme/maps/taxi/TaxiManager.cpp b/android/jni/com/mapswithme/maps/taxi/TaxiManager.cpp index f132469d90..ae5ce25952 100644 --- a/android/jni/com/mapswithme/maps/taxi/TaxiManager.cpp +++ b/android/jni/com/mapswithme/maps/taxi/TaxiManager.cpp @@ -46,7 +46,7 @@ void PrepareClassRefs(JNIEnv * env) "(I[Lcom/mapswithme/maps/taxi/TaxiInfo$Product;)V"); g_taxiInfoErrorConstructor = jni::GetConstructorID(env, g_taxiInfoErrorClass, "(ILjava/lang/String;)V"); - g_taxiLinksClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/taxi/TaxiLinks"); + g_taxiLinksClass = jni::GetGlobalClassRef(env, "com/mapswithme/maps/util/SponsoredLinks"); g_taxiLinksConstructor = jni::GetConstructorID(env, g_taxiLinksClass, "(Ljava/lang/String;Ljava/lang/String;)V"); } diff --git a/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java b/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java index c26a96de21..7116c767fa 100644 --- a/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java +++ b/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java @@ -29,7 +29,7 @@ import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.location.LocationHelper; import com.mapswithme.maps.taxi.TaxiAdapter; import com.mapswithme.maps.taxi.TaxiInfo; -import com.mapswithme.maps.taxi.TaxiLinks; +import com.mapswithme.util.SponsoredLinks; import com.mapswithme.maps.taxi.TaxiManager; import com.mapswithme.maps.widget.DotPager; import com.mapswithme.maps.widget.recycler.DotDividerItemDecoration; @@ -252,7 +252,8 @@ final class RoutingBottomMenuController implements View.OnClickListener { if (RoutingController.get().isTaxiRouterType() && mTaxiInfo != null) { - mStart.setText(Utils.isTaxiAppInstalled(mContext, mTaxiInfo.getType()) + String packageName = TaxiManager.getTaxiPackageName(mTaxiInfo.getType()); + mStart.setText(Utils.isAppInstalled(mContext, packageName) ? R.string.taxi_order : R.string.install_app); mStart.setOnClickListener(new View.OnClickListener() { @@ -291,16 +292,12 @@ final class RoutingBottomMenuController implements View.OnClickListener if (mTaxiProduct == null || mTaxiInfo == null) return; - boolean isTaxiInstalled = Utils.isTaxiAppInstalled(mContext, mTaxiInfo.getType()); MapObject startPoint = RoutingController.get().getStartPoint(); MapObject endPoint = RoutingController.get().getEndPoint(); - TaxiLinks links = TaxiManager.getTaxiLink(mTaxiProduct.getProductId(), mTaxiInfo.getType(), - startPoint, endPoint); + SponsoredLinks links = TaxiManager.getTaxiLink(mTaxiProduct.getProductId(), mTaxiInfo.getType(), + startPoint, endPoint); if (links != null) - { - Utils.launchTaxiApp(mContext, links, mTaxiInfo.getType()); - trackTaxiStatistics(mTaxiInfo.getType(), isTaxiInstalled); - } + launchTaxiApp(links); } void showError(@StringRes int message) @@ -426,4 +423,27 @@ final class RoutingBottomMenuController implements View.OnClickListener break; } } + + private void launchTaxiApp(@Nullable SponsoredLinks links) + { + String packageName = TaxiManager.getTaxiPackageName(mTaxiInfo.getType()); + boolean isTaxiInstalled = Utils.isAppInstalled(mContext, packageName); + Utils.PartnerAppOpenMode openMode = Utils.PartnerAppOpenMode.None; + + switch (mTaxiInfo.getType()) + { + case TaxiManager.PROVIDER_UBER: + openMode = Utils.PartnerAppOpenMode.Direct; + break; + case TaxiManager.PROVIDER_YANDEX: + openMode = Utils.PartnerAppOpenMode.Indirect; + break; + default: + throw new AssertionError("Unsupported taxi type: " + mTaxiInfo.getType()); + } + + Utils.openPartner(mContext, links, packageName, openMode); + + trackTaxiStatistics(mTaxiInfo.getType(), isTaxiInstalled); + } } diff --git a/android/src/com/mapswithme/maps/taxi/TaxiManager.java b/android/src/com/mapswithme/maps/taxi/TaxiManager.java index b4a2c3516f..b7c7ebc0a5 100644 --- a/android/src/com/mapswithme/maps/taxi/TaxiManager.java +++ b/android/src/com/mapswithme/maps/taxi/TaxiManager.java @@ -7,6 +7,7 @@ import android.support.annotation.Nullable; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.util.NetworkPolicy; +import com.mapswithme.util.SponsoredLinks; import com.mapswithme.util.concurrency.UiThread; import java.lang.annotation.Retention; @@ -84,8 +85,8 @@ public class TaxiManager } @Nullable - public static TaxiLinks getTaxiLink(@NonNull String productId, @TaxiType int type, - @Nullable MapObject startPoint, @Nullable MapObject endPoint) + public static SponsoredLinks getTaxiLink(@NonNull String productId, @TaxiType int type, + @Nullable MapObject startPoint, @Nullable MapObject endPoint) { if (startPoint == null || endPoint == null) return null; @@ -96,6 +97,20 @@ public class TaxiManager endPoint.getLon()); } + @NonNull + public static String getTaxiPackageName(@TaxiManager.TaxiType int type) + { + switch (type) + { + case TaxiManager.PROVIDER_UBER: + return "com.ubercab"; + case TaxiManager.PROVIDER_YANDEX: + return "ru.yandex.taxi"; + default: + throw new AssertionError("Unsupported taxi type: " + type); + } + } + public void setTaxiListener(@Nullable TaxiListener listener) { mListener = listener; @@ -105,9 +120,9 @@ public class TaxiManager double srcLon, double dstLat, double dstLon); @NonNull - public native TaxiLinks nativeGetTaxiLinks(@NonNull NetworkPolicy policy, @TaxiType int type, - @NonNull String productId, double srcLon, - double srcLat, double dstLat, double dstLon); + public native SponsoredLinks nativeGetTaxiLinks(@NonNull NetworkPolicy policy, @TaxiType int type, + @NonNull String productId, double srcLon, + double srcLat, double dstLat, double dstLon); public enum ErrorCode { diff --git a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java index 32b8d501ec..e6e5ce9950 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java +++ b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java @@ -84,6 +84,7 @@ import com.mapswithme.maps.widget.recycler.RecyclerClickListener; import com.mapswithme.util.ConnectionState; import com.mapswithme.util.Graphics; import com.mapswithme.util.NetworkPolicy; +import com.mapswithme.util.SponsoredLinks; import com.mapswithme.util.StringUtils; import com.mapswithme.util.ThemeUtils; import com.mapswithme.util.UiUtils; @@ -941,6 +942,8 @@ public class PlacePageView extends RelativeLayout if (info == null) return; + Utils.PartnerAppOpenMode partnerAppOpenMode = Utils.PartnerAppOpenMode.None; + switch (info.getType()) { case Sponsored.TYPE_BOOKING: @@ -949,6 +952,7 @@ public class PlacePageView extends RelativeLayout if (book) { + partnerAppOpenMode = Utils.PartnerAppOpenMode.Direct; Statistics.INSTANCE.trackBookHotelEvent(info, mMapObject); } else @@ -971,7 +975,17 @@ public class PlacePageView extends RelativeLayout try { - Utils.openUrl(getContext(), book ? info.getUrl() : info.getDescriptionUrl()); + if (partnerAppOpenMode != Utils.PartnerAppOpenMode.None) + { + SponsoredLinks links = new SponsoredLinks(info.getDeepLink(), info.getUrl()); + String packageName = Sponsored.GetPackageName(info.getType()); + + Utils.openPartner(getContext(), links, packageName, partnerAppOpenMode); + } + else + { + Utils.openUrl(getContext(), book ? info.getUrl() : info.getDescriptionUrl()); + } } catch (ActivityNotFoundException e) { diff --git a/android/src/com/mapswithme/maps/widget/placepage/Sponsored.java b/android/src/com/mapswithme/maps/widget/placepage/Sponsored.java index 63e729cf4e..f6d3882ced 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/Sponsored.java +++ b/android/src/com/mapswithme/maps/widget/placepage/Sponsored.java @@ -201,6 +201,8 @@ public final class Sponsored @NonNull private final String mUrl; @NonNull + private final String mDeepLink; + @NonNull private final String mDescriptionUrl; @NonNull private final String mReviewUrl; @@ -210,14 +212,16 @@ public final class Sponsored @NonNull private final String mPartnerName; - public Sponsored(@NonNull String rating, @UGC.Impress int impress, @NonNull String price, - @NonNull String url, @NonNull String descriptionUrl, @NonNull String reviewUrl, - @SponsoredType int type, int partnerIndex, @NonNull String partnerName) + private Sponsored(@NonNull String rating, @UGC.Impress int impress, @NonNull String price, + @NonNull String url, @NonNull String deepLink, @NonNull String descriptionUrl, + @NonNull String reviewUrl, @SponsoredType int type, int partnerIndex, + @NonNull String partnerName) { mRating = rating; mImpress = impress; mPrice = price; mUrl = url; + mDeepLink = deepLink; mDescriptionUrl = descriptionUrl; mReviewUrl = reviewUrl; mType = type; @@ -260,6 +264,12 @@ public final class Sponsored return mUrl; } + @NonNull + public String getDeepLink() + { + return mDeepLink; + } + @NonNull String getDescriptionUrl() { @@ -379,6 +389,18 @@ public final class Sponsored listener.onHotelInfoReceived(id, info); } + @NonNull + public static String GetPackageName(@SponsoredType int type) + { + switch (type) + { + case Sponsored.TYPE_BOOKING: + return "com.booking"; + default: + return ""; + } + } + @Nullable public static native Sponsored nativeGetCurrent(); diff --git a/android/src/com/mapswithme/maps/taxi/TaxiLinks.java b/android/src/com/mapswithme/util/SponsoredLinks.java similarity index 72% rename from android/src/com/mapswithme/maps/taxi/TaxiLinks.java rename to android/src/com/mapswithme/util/SponsoredLinks.java index dc979bf399..218fa0496a 100644 --- a/android/src/com/mapswithme/maps/taxi/TaxiLinks.java +++ b/android/src/com/mapswithme/util/SponsoredLinks.java @@ -1,15 +1,15 @@ -package com.mapswithme.maps.taxi; +package com.mapswithme.util; import android.support.annotation.NonNull; -public class TaxiLinks +public class SponsoredLinks { @NonNull private final String mDeepLink; @NonNull private final String mUniversalLink; - public TaxiLinks(@NonNull String deepLink, @NonNull String universalLink) + public SponsoredLinks(@NonNull String deepLink, @NonNull String universalLink) { mDeepLink = deepLink; mUniversalLink = universalLink; diff --git a/android/src/com/mapswithme/util/Utils.java b/android/src/com/mapswithme/util/Utils.java index 721bb9bac2..a358ba4387 100644 --- a/android/src/com/mapswithme/util/Utils.java +++ b/android/src/com/mapswithme/util/Utils.java @@ -32,7 +32,6 @@ import com.mapswithme.maps.BuildConfig; import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.R; import com.mapswithme.maps.activity.CustomNavigateUpListener; -import com.mapswithme.maps.taxi.TaxiLinks; import com.mapswithme.maps.taxi.TaxiManager; import com.mapswithme.util.concurrency.UiThread; import com.mapswithme.util.log.Logger; @@ -304,7 +303,7 @@ public class Utils return installationId; } - private static boolean isAppInstalled(@NonNull Activity context, @NonNull String packageName) + public static boolean isAppInstalled(@NonNull Context context, @NonNull String packageName) { try { @@ -317,57 +316,47 @@ public class Utils } } - public static boolean isTaxiAppInstalled(@NonNull Activity context, @TaxiManager.TaxiType int type) + public static void launchAppDirect(@NonNull Context context, @NonNull SponsoredLinks links, + String packageName) { - switch (type) - { - case TaxiManager.PROVIDER_UBER: - return Utils.isAppInstalled(context, "com.ubercab"); - case TaxiManager.PROVIDER_YANDEX: - return Utils.isAppInstalled(context, "ru.yandex.taxi"); - default: - throw new AssertionError("Unsupported taxi type: " + type); - } - } + boolean isAppInstalled = Utils.isAppInstalled(context, packageName); - public static void launchTaxiApp(@NonNull Activity context, @NonNull TaxiLinks links, - @TaxiManager.TaxiType int type) - { - switch (type) + if (!isAppInstalled) { - case TaxiManager.PROVIDER_UBER: - launchUber(context, links, isTaxiAppInstalled(context, type)); - break; - case TaxiManager.PROVIDER_YANDEX: - launchYandexTaxi(context, links); - break; - default: - throw new AssertionError("Unsupported taxi type: " + type); + openUrl(context, links.getUniversalLink()); + return; } - } - private static void launchUber(@NonNull Activity context, @NonNull TaxiLinks links, boolean isAppInstalled) - { final Intent intent = new Intent(Intent.ACTION_VIEW); - if (isAppInstalled) - { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setData(Uri.parse(links.getDeepLink())); - } else - { - // No Taxi app! Open mobile website. - intent.setData(Uri.parse(links.getUniversalLink())); - } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setData(Uri.parse(links.getDeepLink())); context.startActivity(intent); } - private static void launchYandexTaxi(@NonNull Activity context, @NonNull TaxiLinks links) + public static void launchAppIndirect(@NonNull Context context, @NonNull SponsoredLinks links) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(links.getDeepLink())); context.startActivity(intent); } + public static void openPartner(@NonNull Context activity, @NonNull SponsoredLinks links, + @NonNull String packageName, @NonNull PartnerAppOpenMode openMode) + { + switch (openMode) + { + case Direct: + launchAppDirect(activity, links, packageName); + break; + case Indirect: + launchAppIndirect(activity, links); + break; + default: + throw new AssertionError("Unsupported partner app open mode: " + openMode + + "; Package name: " + packageName); + } + } + public static void sendTo(@NonNull Context context, @NonNull String email) { Intent intent = new Intent(Intent.ACTION_SENDTO); @@ -562,4 +551,9 @@ public class Utils return Character.toLowerCase(src.charAt(0)) + src.substring(1); } + + public enum PartnerAppOpenMode + { + None, Direct, Indirect + } } diff --git a/iphone/Maps/MAPSME.plist b/iphone/Maps/MAPSME.plist index e058fb1119..7327b8f105 100644 --- a/iphone/Maps/MAPSME.plist +++ b/iphone/Maps/MAPSME.plist @@ -108,6 +108,7 @@ uber yandextaxi tel + booking LSRequiresIPhoneOS diff --git a/iphone/Maps/UI/PlacePage/MWMPlacePageData.h b/iphone/Maps/UI/PlacePage/MWMPlacePageData.h index b728b092bc..69c2a1f95c 100644 --- a/iphone/Maps/UI/PlacePage/MWMPlacePageData.h +++ b/iphone/Maps/UI/PlacePage/MWMPlacePageData.h @@ -250,6 +250,7 @@ using NewSectionsAreReady = void (^)(NSRange const & range, MWMPlacePageData * d - (BOOL)isMyPosition; - (BOOL)isRoutePoint; - (BOOL)isPreviewExtended; +- (BOOL)isPartnerAppInstalled; + (MWMRatingSummaryViewValueType)ratingValueType:(place_page::rating::Impress)impress; diff --git a/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm b/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm index 6d941ab07a..cae6ef593a 100644 --- a/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm +++ b/iphone/Maps/UI/PlacePage/MWMPlacePageData.mm @@ -519,10 +519,12 @@ NSString * const kUserDefaultsLatLonAsDMSKey = @"UserDefaultsLatLonAsDMS"; - (NSString *)bookingApproximatePricing { return self.isBooking ? @(m_info.GetApproximatePricing().c_str()) : nil; } - (NSURL *)sponsoredURL { - // There are sponsors without URL. For such psrtners we do not show special button. - if (m_info.IsSponsored() && !m_info.GetSponsoredUrl().empty()) + auto const link = self.isPartnerAppInstalled ? m_info.GetSponsoredDeepLink() : m_info.GetSponsoredUrl(); + + // There are sponsors without URL. For such partners we do not show special button. + if (m_info.IsSponsored() && !link.empty()) { - auto urlString = [@(m_info.GetSponsoredUrl().c_str()) + auto urlString = [@(link.c_str()) stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet .URLQueryAllowedCharacterSet]; auto url = [NSURL URLWithString:urlString]; @@ -788,6 +790,16 @@ NSString * const kUserDefaultsLatLonAsDMSKey = @"UserDefaultsLatLonAsDMS"; - (BOOL)isHTMLDescription { return strings::IsHTML(m_info.GetBookmarkData().GetDescription()); } - (BOOL)isRoutePoint { return m_info.IsRoutePoint(); } - (BOOL)isPreviewExtended { return m_info.IsPreviewExtended(); } +- (BOOL)isPartnerAppInstalled +{ + NSURL * url; + switch (m_info.GetSponsoredType()) + { + case SponsoredType::Booking: url = [NSURL URLWithString:@"booking://"]; break; + default: return NO; + } + return [UIApplication.sharedApplication canOpenURL:url]; +} + (MWMRatingSummaryViewValueType)ratingValueType:(rating::Impress)impress { diff --git a/iphone/Maps/UI/PlacePage/MWMPlacePageManager.mm b/iphone/Maps/UI/PlacePage/MWMPlacePageManager.mm index 98156cc4f9..fb332437b0 100644 --- a/iphone/Maps/UI/PlacePage/MWMPlacePageManager.mm +++ b/iphone/Maps/UI/PlacePage/MWMPlacePageManager.mm @@ -484,7 +484,11 @@ void logSponsoredEvent(MWMPlacePageData * data, NSString * eventName) logSponsoredEvent(data, eventName); NSURL * url = isDescription ? data.sponsoredDescriptionURL : data.sponsoredURL; NSAssert(url, @"Sponsored url can't be nil!"); - [self.ownerViewController openUrl:url]; + + if (data.isPartnerAppInstalled) + [UIApplication.sharedApplication openURL:url]; + else + [self.ownerViewController openUrl:url]; } - (void)searchBookingHotels