diff --git a/android/3rd_party/myTarget.jar b/android/3rd_party/myTarget.jar deleted file mode 100644 index 1715d1e5a0..0000000000 Binary files a/android/3rd_party/myTarget.jar and /dev/null differ diff --git a/android/build.gradle b/android/build.gradle index 8df9ebf00d..2716058d43 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,10 +37,10 @@ dependencies { compile 'com.android.support:design:23.2.1' compile 'com.android.support:cardview-v7:23.2.1' // google play services - compile 'com.google.android.gms:play-services-location:8.4.0' - compile 'com.google.android.gms:play-services-analytics:8.4.0' - compile 'com.google.android.gms:play-services-plus:8.4.0' - compile 'com.google.android.gms:play-services-gcm:8.4.0' + compile 'com.google.android.gms:play-services-location:10.0.1' + compile 'com.google.android.gms:play-services-analytics:10.0.1' + compile 'com.google.android.gms:play-services-plus:10.0.1' + compile 'com.google.android.gms:play-services-gcm:10.0.1' // statistics compile 'com.flurry.android:analytics:6.7.0' // crash reporting @@ -54,6 +54,7 @@ dependencies { compile 'com.google.code.gson:gson:2.6.1' compile 'com.pushwoosh:pushwoosh:4.10.7' compile 'com.my.tracker:mytracker-sdk:1.3.5' + compile 'com.my.target:mytarget-sdk:4.6.9' compile fileTree(dir: '3rd_party', include: '*.jar') // BottomSheet compile project(":3rd_party:BottomSheet") diff --git a/android/src/com/mapswithme/maps/ads/CachedMwmNativeAd.java b/android/src/com/mapswithme/maps/ads/CachedMwmNativeAd.java new file mode 100644 index 0000000000..1fc0f52a3c --- /dev/null +++ b/android/src/com/mapswithme/maps/ads/CachedMwmNativeAd.java @@ -0,0 +1,16 @@ +package com.mapswithme.maps.ads; + +abstract class CachedMwmNativeAd implements MwmNativeAd +{ + private final long mLoadedTime; + + CachedMwmNativeAd(long loadedTime) + { + mLoadedTime = loadedTime; + } + + long getLoadedTime() + { + return mLoadedTime; + } +} diff --git a/android/src/com/mapswithme/maps/ads/CachedNativeAdLoader.java b/android/src/com/mapswithme/maps/ads/CachedNativeAdLoader.java new file mode 100644 index 0000000000..4df13ed55f --- /dev/null +++ b/android/src/com/mapswithme/maps/ads/CachedNativeAdLoader.java @@ -0,0 +1,150 @@ +package com.mapswithme.maps.ads; + +import android.content.Context; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mapswithme.util.log.Logger; +import com.mapswithme.util.log.LoggerFactory; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +abstract class CachedNativeAdLoader extends BaseNativeAdLoader +{ + private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final String TAG = CachedNativeAdLoader.class.getSimpleName(); + private static final long REQUEST_INTERVAL_MS = 30 * 1000; + private static final Map CACHE = new HashMap<>(); + private static final Set PENDING_REQUESTS = new HashSet<>(); + + @Nullable + private AdTracker mTracker; + @Nullable + private OnAdCacheModifiedListener mCacheListener; + + CachedNativeAdLoader(@Nullable AdTracker tracker, @Nullable OnAdCacheModifiedListener listener) + { + mTracker = tracker; + mCacheListener = listener; + } + + /** + * Loads an ad for a specified banner id. If there is a cached ad, the caller will be notified + * immediately through {@link NativeAdListener#onAdLoaded(MwmNativeAd)}. + * Otherwise, the caller will be notified once an ad is loaded through the mentioned method. + * + *

Important note: if there is a cached ad for the requested banner id, and that ad + * has a good impression indicator, and there is at least {@link #REQUEST_INTERVAL_MS} between the + * first time that ad was requested and the current time the new ad will be loaded. + * + */ + @Override + public void loadAd(@NonNull Context context, @NonNull String bannerId) + { + LOGGER.d(TAG, "Load the ad for a banner id '" + bannerId + "'"); + + if (isAdLoading(bannerId)) + { + LOGGER.d(TAG, "The ad request for banner id '" + bannerId + "' hasn't been completed yet."); + return; + } + + CachedMwmNativeAd cachedAd = getAdByIdFromCache(bannerId); + + if (cachedAd == null) + { + LOGGER.d(TAG, "There is no an ad in a cache"); + requestAd(context, bannerId); + return; + } + + if (mTracker != null && mTracker.isImpressionGood(bannerId) + && SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS) + { + LOGGER.d(TAG, "A new ad will be loaded because the previous one has a good impression"); + requestAd(context, bannerId); + } + + if (getAdListener() != null) + getAdListener().onAdLoaded(cachedAd); + } + + private void requestAd(@NonNull Context context, @NonNull String bannerId) + { + requestAdForBannerId(context, bannerId); + PENDING_REQUESTS.add(bannerId); + } + + abstract void requestAdForBannerId(@NonNull Context context, @NonNull String bannerId); + + void onError(@NonNull String bannerId) + { + PENDING_REQUESTS.remove(bannerId); + } + + void onAdLoaded(@NonNull String bannerId, @NonNull CachedMwmNativeAd ad) + { + LOGGER.d(TAG, "An ad for id '" + bannerId + "' is loaded"); + PENDING_REQUESTS.remove(bannerId); + + boolean isCacheWasEmpty = isCacheEmptyForId(bannerId); + + LOGGER.d(TAG, "Put the ad to cache"); + putInCache(bannerId, ad); + + if (isCacheWasEmpty && getAdListener() != null) + getAdListener().onAdLoaded(ad); + } + + void onAdClicked(@NonNull String bannerId) + { + if (getAdListener() != null) + { + MwmNativeAd nativeAd = getAdByIdFromCache(bannerId); + if (nativeAd == null) + throw new AssertionError("A facebook native ad must be presented in a cache when it's clicked!"); + + getAdListener().onClick(nativeAd); + } + } + + /** + * Indicates whether the ad is loading right now or not. + * + * @param bannerId A banner id that an ad is loading for. + * @return true if an ad is loading, otherwise - false. + */ + public boolean isAdLoading(@NonNull String bannerId) + { + return PENDING_REQUESTS.contains(bannerId); + } + + private void putInCache(@NonNull String key, @NonNull CachedMwmNativeAd value) + { + CACHE.put(key, value); + if (mCacheListener != null) + mCacheListener.onPut(key); + } + + private void removeFromCache(@NonNull String key, @NonNull CachedMwmNativeAd value) + { + CACHE.remove(key); + if (mCacheListener != null) + mCacheListener.onRemoved(key); + } + + @Nullable + private CachedMwmNativeAd getAdByIdFromCache(@NonNull String bannerId) + { + return CACHE.get(bannerId); + } + + private boolean isCacheEmptyForId(@NonNull String bannerId) + { + return getAdByIdFromCache(bannerId) == null; + } +} diff --git a/android/src/com/mapswithme/maps/ads/FacebookAdsLoader.java b/android/src/com/mapswithme/maps/ads/FacebookAdsLoader.java index d0ecce391c..c2cfa81053 100644 --- a/android/src/com/mapswithme/maps/ads/FacebookAdsLoader.java +++ b/android/src/com/mapswithme/maps/ads/FacebookAdsLoader.java @@ -4,7 +4,6 @@ import android.content.Context; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.UiThread; import com.facebook.ads.Ad; import com.facebook.ads.AdError; @@ -15,86 +14,17 @@ import com.mapswithme.util.log.LoggerFactory; import net.jcip.annotations.NotThreadSafe; import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; @NotThreadSafe -class FacebookAdsLoader extends BaseNativeAdLoader implements AdListener +class FacebookAdsLoader extends CachedNativeAdLoader implements AdListener { private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); private static final String TAG = FacebookAdsLoader.class.getSimpleName(); - private static final long REQUEST_INTERVAL_MS = 30 * 1000; - private static final Map CACHE = new HashMap<>(); - private static final Set PENDING_REQUESTS = new HashSet<>(); - @Nullable - private OnAdCacheModifiedListener mCacheListener; - @Nullable - private AdTracker mTracker; FacebookAdsLoader(@Nullable OnAdCacheModifiedListener cacheListener, @Nullable AdTracker tracker) { - mCacheListener = cacheListener; - mTracker = tracker; - } - - /** - * Loads an ad for a specified banner id. If there is a cached ad, the caller will be notified - * immediately through {@link NativeAdListener#onAdLoaded(MwmNativeAd)}. - * Otherwise, the caller will be notified once an ad is loaded through the mentioned method. - * - *

Important note: if there is a cached ad for the requested banner id, and that ad - * has a good impression indicator, and there is at least {@link #REQUEST_INTERVAL_MS} between the - * first time that ad was requested and the current time the new ad will be loaded. - * - */ - @UiThread - public void loadAd(@NonNull Context context, @NonNull String bannerId) - { - LOGGER.d(TAG, "Load a facebook ad for a banner id '" + bannerId + "'"); - - FacebookNativeAd cachedAd = getAdByIdFromCache(bannerId); - - if (cachedAd == null) - { - LOGGER.d(TAG, "There is no an ad in a cache"); - loadAdInternal(context, bannerId); - return; - } - - if (mTracker != null && mTracker.isImpressionGood(bannerId) - && SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS) - { - LOGGER.d(TAG, "A new ad will be loaded because the previous one has a good impression"); - loadAdInternal(context, bannerId); - } - - if (getAdListener() != null) - getAdListener().onAdLoaded(cachedAd); - } - - /** - * Indicates whether the ad is loading right now or not. - * - * @param bannerId A banner id that an ad is loading for. - * @return true if an ad is loading, otherwise - false. - */ - public boolean isAdLoading(@NonNull String bannerId) - { - return PENDING_REQUESTS.contains(bannerId); - } - - @Nullable - private FacebookNativeAd getAdByIdFromCache(@NonNull String bannerId) - { - return CACHE.get(bannerId); - } - - private boolean isCacheEmptyForId(@NonNull String bannerId) - { - return getAdByIdFromCache(bannerId) == null; + super(tracker, cacheListener); } @Override @@ -102,7 +32,8 @@ class FacebookAdsLoader extends BaseNativeAdLoader implements AdListener { LOGGER.w(TAG, "A error '" + adError.getErrorMessage() + "' is occurred while loading " + "an ad for banner id '" + ad.getPlacementId() + "'"); - PENDING_REQUESTS.remove(ad.getPlacementId()); + + onError(ad.getPlacementId()); if (getAdListener() != null) getAdListener().onError(new FacebookNativeAd((NativeAd)ad), new FacebookAdError(adError)); } @@ -110,58 +41,22 @@ class FacebookAdsLoader extends BaseNativeAdLoader implements AdListener @Override public void onAdLoaded(Ad ad) { - LOGGER.d(TAG, "An ad for id '" + ad.getPlacementId() + "' is loaded"); - PENDING_REQUESTS.remove(ad.getPlacementId()); - - boolean isCacheWasEmpty = isCacheEmptyForId(ad.getPlacementId()); - - LOGGER.d(TAG, "Put a facebook ad to cache"); - MwmNativeAd nativeAd = new FacebookNativeAd((NativeAd) ad, SystemClock.elapsedRealtime()); - putInCache(ad.getPlacementId(), (FacebookNativeAd) nativeAd); - - if (isCacheWasEmpty && getAdListener() != null) - getAdListener().onAdLoaded(nativeAd); + CachedMwmNativeAd nativeAd = new FacebookNativeAd((NativeAd) ad, SystemClock.elapsedRealtime()); + onAdLoaded(ad.getPlacementId(), nativeAd); } @Override public void onAdClicked(Ad ad) { - if (getAdListener() != null) - { - MwmNativeAd nativeAd = getAdByIdFromCache(ad.getPlacementId()); - if (nativeAd == null) - throw new AssertionError("A facebook native ad must be presented in a cache when it's clicked!"); - - getAdListener().onClick(nativeAd); - } + onAdClicked(ad.getPlacementId()); } - private void loadAdInternal(@NonNull Context context, @NonNull String bannerId) + @Override + void requestAdForBannerId(@NonNull Context context, @NonNull String bannerId) { - if (PENDING_REQUESTS.contains(bannerId)) - { - LOGGER.d(TAG, "The ad request for banner id '" + bannerId + "' hasn't been completed yet."); - return; - } - NativeAd ad = new NativeAd(context, bannerId); ad.setAdListener(this); LOGGER.d(TAG, "Loading is started"); ad.loadAd(EnumSet.of(NativeAd.MediaCacheFlag.ICON)); - PENDING_REQUESTS.add(bannerId); - } - - private void putInCache(@NonNull String key, @NonNull FacebookNativeAd value) - { - CACHE.put(key, value); - if (mCacheListener != null) - mCacheListener.onPut(key); - } - - private void removeFromCache(@NonNull String key, @NonNull FacebookNativeAd value) - { - CACHE.remove(key); - if (mCacheListener != null) - mCacheListener.onRemoved(key); } } diff --git a/android/src/com/mapswithme/maps/ads/FacebookNativeAd.java b/android/src/com/mapswithme/maps/ads/FacebookNativeAd.java index e7f821d351..4098cc89ce 100644 --- a/android/src/com/mapswithme/maps/ads/FacebookNativeAd.java +++ b/android/src/com/mapswithme/maps/ads/FacebookNativeAd.java @@ -10,29 +10,23 @@ import com.mapswithme.maps.R; import java.util.ArrayList; import java.util.List; -class FacebookNativeAd implements MwmNativeAd +class FacebookNativeAd extends CachedMwmNativeAd { @NonNull private final NativeAd mAd; - private final long mLoadedTime; FacebookNativeAd(@NonNull NativeAd ad, long timestamp) { - mLoadedTime = timestamp; + super(timestamp); mAd = ad; } FacebookNativeAd(@NonNull NativeAd ad) { - mLoadedTime = 0; + super(0); mAd = ad; } - long getLoadedTime() - { - return mLoadedTime; - } - @NonNull @Override public String getTitle() diff --git a/android/src/com/mapswithme/maps/ads/Factory.java b/android/src/com/mapswithme/maps/ads/Factory.java index b9e3f3f4b5..86b101cfec 100644 --- a/android/src/com/mapswithme/maps/ads/Factory.java +++ b/android/src/com/mapswithme/maps/ads/Factory.java @@ -11,4 +11,11 @@ public class Factory { return new FacebookAdsLoader(cacheListener, tracker); } + + @NonNull + public static NativeAdLoader createMyTargetAdLoader(@Nullable OnAdCacheModifiedListener cacheListener, + @Nullable AdTracker tracker) + { + return new MyTargetAdsLoader(cacheListener, tracker); + } } diff --git a/android/src/com/mapswithme/maps/ads/MyTargetAdError.java b/android/src/com/mapswithme/maps/ads/MyTargetAdError.java new file mode 100644 index 0000000000..dd7014e1a5 --- /dev/null +++ b/android/src/com/mapswithme/maps/ads/MyTargetAdError.java @@ -0,0 +1,28 @@ +package com.mapswithme.maps.ads; + +import android.support.annotation.Nullable; + + +class MyTargetAdError implements NativeAdError +{ + @Nullable + private final String mMessage; + + MyTargetAdError(@Nullable String message) + { + mMessage = message; + } + + @Nullable + @Override + public String getMessage() + { + return mMessage; + } + + @Override + public int getCode() + { + return 0; + } +} diff --git a/android/src/com/mapswithme/maps/ads/MyTargetAdsLoader.java b/android/src/com/mapswithme/maps/ads/MyTargetAdsLoader.java new file mode 100644 index 0000000000..d3e7c91832 --- /dev/null +++ b/android/src/com/mapswithme/maps/ads/MyTargetAdsLoader.java @@ -0,0 +1,62 @@ +package com.mapswithme.maps.ads; + +import android.content.Context; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mapswithme.util.log.Logger; +import com.mapswithme.util.log.LoggerFactory; +import com.my.target.ads.CustomParams; +import com.my.target.nativeads.NativeAd; + +class MyTargetAdsLoader extends CachedNativeAdLoader implements NativeAd.NativeAdListener +{ + private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final String TAG = MyTargetAdsLoader.class.getSimpleName(); + //FIXME: read correct slot from private.h + private static final int SLOT = 93418; + private static final String ZONE_KEY_PARAMETER = "_SITEZONE"; + + MyTargetAdsLoader(@Nullable OnAdCacheModifiedListener listener, @Nullable AdTracker tracker) + { + super(tracker, listener); + } + + @Override + void requestAdForBannerId(@NonNull Context context, @NonNull String bannerId) + { + NativeAd ad = new NativeAd(SLOT, context); + ad.setListener(this); + //TODO: use parametr banner Id + ad.getCustomParams().setCustomParam(ZONE_KEY_PARAMETER, "3"); + ad.load(); + } + + @Override + public void onLoad(NativeAd nativeAd) + { + CustomParams params = nativeAd.getCustomParams(); + String bannerId = params.getData().get(ZONE_KEY_PARAMETER); + onAdLoaded(bannerId, new MyTargetNativeAd(nativeAd, SystemClock.elapsedRealtime())); + } + + @Override + public void onNoAd(String s, NativeAd nativeAd) + { + LOGGER.w(TAG, "onNoAd s = " + s); + CustomParams params = nativeAd.getCustomParams(); + String bannerId = params.getData().get(ZONE_KEY_PARAMETER); + onError(bannerId); + if (getAdListener() != null) + getAdListener().onError(new MyTargetNativeAd(nativeAd, 0), new MyTargetAdError(s)); + } + + @Override + public void onClick(NativeAd nativeAd) + { + CustomParams params = nativeAd.getCustomParams(); + String bannerId = params.getData().get(ZONE_KEY_PARAMETER); + onAdClicked(bannerId); + } +} diff --git a/android/src/com/mapswithme/maps/ads/MyTargetNativeAd.java b/android/src/com/mapswithme/maps/ads/MyTargetNativeAd.java new file mode 100644 index 0000000000..69bbef6138 --- /dev/null +++ b/android/src/com/mapswithme/maps/ads/MyTargetNativeAd.java @@ -0,0 +1,64 @@ +package com.mapswithme.maps.ads; + +import android.support.annotation.NonNull; +import android.view.View; +import android.widget.ImageView; + +import com.my.target.nativeads.NativeAd; +import com.my.target.nativeads.banners.NativePromoBanner; +import com.my.target.nativeads.models.ImageData; + +class MyTargetNativeAd extends CachedMwmNativeAd +{ + @NonNull + private final NativeAd mAd; + + MyTargetNativeAd(@NonNull NativeAd ad, long timestamp) + { + super(timestamp); + mAd = ad; + } + + @NonNull + @Override + public String getTitle() + { + return mAd.getBanner().getTitle(); + } + + @NonNull + @Override + public String getDescription() + { + return mAd.getBanner().getDescription(); + } + + @NonNull + @Override + public String getAction() + { + return mAd.getBanner().getCtaText(); + } + + @Override + public void loadIcon(@NonNull View view) + { + NativePromoBanner banner = mAd.getBanner(); + ImageData icon = banner.getIcon(); + if (icon != null) + NativeAd.loadImageToView(icon, (ImageView) view); + } + + @Override + public void registerView(@NonNull View bannerView) + { + mAd.registerView(bannerView); + } + + @NonNull + @Override + public String getProvider() + { + return "MY_TARGET"; + } +} diff --git a/android/src/com/mapswithme/maps/downloader/DownloaderAdapter.java b/android/src/com/mapswithme/maps/downloader/DownloaderAdapter.java index 8eab650312..0cd7caf49e 100644 --- a/android/src/com/mapswithme/maps/downloader/DownloaderAdapter.java +++ b/android/src/com/mapswithme/maps/downloader/DownloaderAdapter.java @@ -35,9 +35,9 @@ import com.mapswithme.util.ThemeUtils; import com.mapswithme.util.UiUtils; import com.mapswithme.util.statistics.MytargetHelper; import com.mapswithme.util.statistics.Statistics; +import com.my.target.nativeads.banners.NativeAppwallBanner; import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter; import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration; -import ru.mail.android.mytarget.nativeads.banners.NativeAppwallBanner; import java.util.ArrayList; import java.util.Collection; diff --git a/android/src/com/mapswithme/maps/widget/placepage/BannerController.java b/android/src/com/mapswithme/maps/widget/placepage/BannerController.java index 426454f9a7..a084977aad 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/BannerController.java +++ b/android/src/com/mapswithme/maps/widget/placepage/BannerController.java @@ -269,6 +269,7 @@ final class BannerController @Override public void onAdLoaded(@NonNull MwmNativeAd ad) { + LOGGER.d(TAG, "onAdLoaded, title = " + ad.getTitle() + " provider = " + ad.getProvider()); if (mBanner == null || TextUtils.isEmpty(mBanner.getId())) return; diff --git a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java index 227632f340..5c5be3c7bd 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java +++ b/android/src/com/mapswithme/maps/widget/placepage/PlacePageView.java @@ -384,7 +384,7 @@ public class PlacePageView extends RelativeLayout if (bannerView != null) { DefaultAdTracker tracker = new DefaultAdTracker(); - NativeAdLoader loader = Factory.createFacebookAdLoader(tracker, tracker); + NativeAdLoader loader = Factory.createMyTargetAdLoader(tracker, tracker); mBannerController = new BannerController(bannerView, this, loader, tracker); } diff --git a/android/src/com/mapswithme/util/statistics/MytargetHelper.java b/android/src/com/mapswithme/util/statistics/MytargetHelper.java index 88e300b891..840bd148cb 100644 --- a/android/src/com/mapswithme/util/statistics/MytargetHelper.java +++ b/android/src/com/mapswithme/util/statistics/MytargetHelper.java @@ -1,20 +1,16 @@ package com.mapswithme.util.statistics; import android.app.Activity; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import com.mapswithme.maps.MwmApplication; import com.mapswithme.maps.PrivateVariables; -import com.mapswithme.maps.R; import com.mapswithme.util.ConnectionState; import com.mapswithme.util.concurrency.ThreadPool; import com.mapswithme.util.concurrency.UiThread; -import ru.mail.android.mytarget.core.net.Hosts; -import ru.mail.android.mytarget.nativeads.NativeAppwallAd; -import ru.mail.android.mytarget.nativeads.banners.NativeAppwallBanner; +import com.my.target.nativeads.NativeAppwallAd; +import com.my.target.nativeads.banners.NativeAppwallBanner; import java.io.IOException; import java.net.HttpURLConnection; @@ -45,11 +41,6 @@ public final class MytargetHelper void onDataReady(@Nullable T data); } - static - { - Hosts.setMyComHost(); - } - public MytargetHelper(final @NonNull Listener listener) { if (!ConnectionState.isConnected() || !isShowcaseSwitchedOnLocal())