forked from organicmaps/organicmaps
[android] Added visibility listener mechanism for tracking ads in PP
This commit is contained in:
parent
e996549c5e
commit
2e0077ac23
8 changed files with 236 additions and 95 deletions
|
@ -999,6 +999,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
mNavigationController.onResume();
|
||||
if (mNavAnimationController != null)
|
||||
mNavAnimationController.onResume();
|
||||
mPlacePage.onActivityResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1041,6 +1042,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
|||
TtsPlayer.INSTANCE.stop();
|
||||
LikesManager.INSTANCE.cancelDialogs();
|
||||
mOnmapDownloader.onPause();
|
||||
mPlacePage.onActivityPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
|
9
android/src/com/mapswithme/maps/ads/AdTracker.java
Normal file
9
android/src/com/mapswithme/maps/ads/AdTracker.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package com.mapswithme.maps.ads;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
interface AdTracker
|
||||
{
|
||||
void start(@NonNull String bannerId);
|
||||
void stop(@NonNull String bannerId);
|
||||
}
|
|
@ -2,7 +2,6 @@ package com.mapswithme.maps.ads;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
@ -22,57 +21,47 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
@NotThreadSafe
|
||||
public class FacebookAdsLoader
|
||||
public class FacebookAdsLoader 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 EXPIRATION_TIME_MS = 60 * 60 * 1000;
|
||||
private static final Map<String, FacebookAd> CACHE = new HashMap<>();
|
||||
private static final Set<String> PENDING_REQUESTS = new HashSet<>();
|
||||
private static final long REQUEST_INTERVAL_MS = 30 * 1000;
|
||||
private final Map<String, FacebookAd> mCache = new HashMap<>();
|
||||
private final Set<String> mPendingRequests = new HashSet<>();
|
||||
@Nullable
|
||||
private FacebookAdsListener mAdsListener;
|
||||
@Nullable
|
||||
private OnAdCacheModifiedListener mCacheListener;
|
||||
|
||||
/**
|
||||
* Loads an ad for a specified placement id. If there is a cached ad, and it's not expired,
|
||||
* that ad will be returned immediately. Otherwise, this method returns null, and a listener will
|
||||
* be notified when a requested ad is loaded.
|
||||
* that ad will be returned immediately. Otherwise, this method returns null, and {@link #mAdsListener} will
|
||||
* be notified when the requested ad is loaded.
|
||||
*
|
||||
* @param context An activity context.
|
||||
* @param placementId A placement id that ad will be loaded for.
|
||||
* @param listener A listener to be notified when an ad is loaded.
|
||||
* @return A cached banner if it presents, otherwise <code>null</code>
|
||||
*/
|
||||
@Nullable
|
||||
@UiThread
|
||||
public NativeAd load(@NonNull Context context, @NonNull String placementId,
|
||||
@NonNull FacebookAdsListener listener)
|
||||
public NativeAd load(@NonNull Context context, @NonNull String placementId)
|
||||
{
|
||||
LOGGER.d(TAG, "Load a facebook ad for a placement id '" + placementId + "'");
|
||||
if (!PENDING_REQUESTS.contains(placementId))
|
||||
{
|
||||
NativeAd ad = new NativeAd(context, placementId);
|
||||
ad.setAdListener(listener);
|
||||
ad.loadAd(EnumSet.of(NativeAd.MediaCacheFlag.ICON));
|
||||
LOGGER.d(TAG, "Loading is started");
|
||||
PENDING_REQUESTS.add(placementId);
|
||||
} else
|
||||
{
|
||||
LOGGER.d(TAG, "The ad request for placement id '" + placementId + "' hasn't been completed yet.");
|
||||
}
|
||||
|
||||
FacebookAd cachedAd = CACHE.get(placementId);
|
||||
FacebookAd cachedAd = mCache.get(placementId);
|
||||
|
||||
if (cachedAd == null)
|
||||
{
|
||||
LOGGER.d(TAG, "There is no an ad in a cache for a placement id '" + placementId + "'");
|
||||
LOGGER.d(TAG, "There is no an ad in a cache");
|
||||
loadAdInternal(context, placementId);
|
||||
return null;
|
||||
}
|
||||
|
||||
long cacheTime = SystemClock.elapsedRealtime() - cachedAd.getLoadedTime();
|
||||
if (cacheTime >= EXPIRATION_TIME_MS)
|
||||
if (/** Tracker.checkShowTime(placmenetId) &&**/
|
||||
SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS)
|
||||
{
|
||||
LOGGER.d(TAG, "A facebook ad is expired for a placement id '" + placementId + "'. " +
|
||||
"An expiration time is " + (cacheTime - EXPIRATION_TIME_MS) + " ms");
|
||||
CACHE.remove(placementId);
|
||||
return null;
|
||||
LOGGER.d(TAG, "Ad should be reloaded");
|
||||
loadAdInternal(context, placementId);
|
||||
}
|
||||
|
||||
return cachedAd.getAd();
|
||||
|
@ -84,9 +73,9 @@ public class FacebookAdsLoader
|
|||
* @param placementId A placement id that an ad is loading for.
|
||||
* @return true if an ad is loading, otherwise - false.
|
||||
*/
|
||||
public static boolean isAdLoadingForId(@NonNull String placementId)
|
||||
public boolean isAdLoadingForId(@NonNull String placementId)
|
||||
{
|
||||
return PENDING_REQUESTS.contains(placementId);
|
||||
return mPendingRequests.contains(placementId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,9 +84,9 @@ public class FacebookAdsLoader
|
|||
* @return A cached ad or <code>null</code> if it doesn't exist.
|
||||
*/
|
||||
@Nullable
|
||||
public static NativeAd getAdByIdFromCache(@NonNull String placementId)
|
||||
public NativeAd getAdByIdFromCache(@NonNull String placementId)
|
||||
{
|
||||
FacebookAd ad = CACHE.get(placementId);
|
||||
FacebookAd ad = mCache.get(placementId);
|
||||
return ad != null ? ad.getAd() : null;
|
||||
}
|
||||
|
||||
|
@ -106,38 +95,84 @@ public class FacebookAdsLoader
|
|||
*
|
||||
* @return <code>true</code> if there is a cached ad, otherwise - <code>false</code>
|
||||
*/
|
||||
public static boolean isCacheEmptyForId(@NonNull String placementId)
|
||||
public boolean isCacheEmptyForId(@NonNull String placementId)
|
||||
{
|
||||
return getAdByIdFromCache(placementId) == null;
|
||||
}
|
||||
|
||||
public abstract static class FacebookAdsListener implements AdListener
|
||||
@Override
|
||||
public void onError(Ad ad, AdError adError)
|
||||
{
|
||||
@Override
|
||||
public final void onAdLoaded(Ad ad)
|
||||
LOGGER.w(TAG, "A error '" + adError + "' is occurred while loading an ad for placement id " +
|
||||
"'" + ad.getPlacementId() + "'");
|
||||
mPendingRequests.remove(ad.getPlacementId());
|
||||
if (mAdsListener != null)
|
||||
mAdsListener.onError(ad, adError, isCacheEmptyForId(ad.getPlacementId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdLoaded(Ad ad)
|
||||
{
|
||||
LOGGER.d(TAG, "An ad for id '" + ad.getPlacementId() + "' is loaded");
|
||||
mPendingRequests.remove(ad.getPlacementId());
|
||||
|
||||
boolean isCacheWasEmpty = isCacheEmptyForId(ad.getPlacementId());
|
||||
|
||||
LOGGER.d(TAG, "Put a facebook ad to cache");
|
||||
putInCache(ad.getPlacementId(), new FacebookAd((NativeAd)ad, SystemClock.elapsedRealtime()));
|
||||
|
||||
if (isCacheWasEmpty && mAdsListener != null)
|
||||
mAdsListener.onFacebookAdLoaded((NativeAd) ad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdClicked(Ad ad)
|
||||
{
|
||||
if (mAdsListener != null)
|
||||
mAdsListener.onAdClicked(ad);
|
||||
}
|
||||
|
||||
public void setAdsListener(@Nullable FacebookAdsListener adsListener)
|
||||
{
|
||||
mAdsListener = adsListener;
|
||||
}
|
||||
|
||||
public void setCacheListener(@Nullable OnAdCacheModifiedListener cacheListener)
|
||||
{
|
||||
mCacheListener = cacheListener;
|
||||
}
|
||||
|
||||
private void loadAdInternal(@NonNull Context context, @NonNull String placementId)
|
||||
{
|
||||
if (mPendingRequests.contains(placementId))
|
||||
{
|
||||
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 for a placement id '" + ad.getPlacementId());
|
||||
CACHE.put(ad.getPlacementId(), new FacebookAd((NativeAd)ad, SystemClock.elapsedRealtime()));
|
||||
|
||||
if (isCacheWasEmpty)
|
||||
onFacebookAdLoaded((NativeAd) ad);
|
||||
LOGGER.d(TAG, "The ad request for placement id '" + placementId + "' hasn't been completed yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public final void onError(Ad ad, AdError adError)
|
||||
{
|
||||
LOGGER.w(TAG, "A error '" + adError + "' is occurred while loading an ad for placement id " +
|
||||
"'" + ad.getPlacementId() + "'");
|
||||
PENDING_REQUESTS.remove(ad.getPlacementId());
|
||||
onError(ad, adError, isCacheEmptyForId(ad.getPlacementId()));
|
||||
}
|
||||
NativeAd ad = new NativeAd(context, placementId);
|
||||
ad.setAdListener(this);
|
||||
LOGGER.d(TAG, "Loading is started");
|
||||
ad.loadAd(EnumSet.of(NativeAd.MediaCacheFlag.ICON));
|
||||
mPendingRequests.add(placementId);
|
||||
}
|
||||
|
||||
private void putInCache(@NonNull String key, @NonNull FacebookAd value)
|
||||
{
|
||||
mCache.put(key, value);
|
||||
if (mCacheListener != null)
|
||||
mCacheListener.onPut(key);
|
||||
}
|
||||
|
||||
private void removeFromCache(@NonNull String key, @NonNull FacebookAd value)
|
||||
{
|
||||
mCache.remove(key);
|
||||
if (mCacheListener != null)
|
||||
mCacheListener.onRemove(key);
|
||||
}
|
||||
|
||||
public interface FacebookAdsListener
|
||||
{
|
||||
/**
|
||||
* Called <b>only if</b> there is no a cached ad for a corresponding placement id
|
||||
* contained within a downloaded ad object, i.e. {@link NativeAd#getPlacementId()}.
|
||||
|
@ -147,7 +182,7 @@ public class FacebookAdsLoader
|
|||
* @param ad A downloaded ad.
|
||||
*/
|
||||
@UiThread
|
||||
protected abstract void onFacebookAdLoaded(@NonNull NativeAd ad);
|
||||
void onFacebookAdLoaded(@NonNull NativeAd ad);
|
||||
|
||||
/**
|
||||
* Notifies about a error occurred while loading the ad.
|
||||
|
@ -156,7 +191,10 @@ public class FacebookAdsLoader
|
|||
* otherwise - false
|
||||
*/
|
||||
@UiThread
|
||||
protected abstract void onError(@NonNull Ad ad, @NonNull AdError adError, boolean isCacheEmpty);
|
||||
void onError(@NonNull Ad ad, @NonNull AdError adError, boolean isCacheEmpty);
|
||||
|
||||
@UiThread
|
||||
void onAdClicked(@NonNull Ad ad);
|
||||
}
|
||||
|
||||
private static class FacebookAd
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.mapswithme.maps.ads;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
interface OnAdCacheModifiedListener
|
||||
{
|
||||
void onRemove(@NonNull String id);
|
||||
void onPut(@NonNull String id);
|
||||
}
|
|
@ -61,4 +61,21 @@ public final class Banner implements Parcelable
|
|||
"mId='" + mId + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Banner banner = (Banner) o;
|
||||
|
||||
return mId != null ? mId.equals(banner.mId) : banner.mId == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return mId != null ? mId.hashCode() : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import static com.mapswithme.util.SharedPropertiesUtils.isShowcaseSwitchedOnLoca
|
|||
import static com.mapswithme.util.statistics.Statistics.EventName.PP_BANNER_CLICK;
|
||||
import static com.mapswithme.util.statistics.Statistics.EventName.PP_BANNER_SHOW;
|
||||
|
||||
final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
||||
final class BannerController
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.INSTANCE
|
||||
.getLogger(LoggerFactory.Type.MISC);
|
||||
|
@ -89,6 +89,7 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
mAds = bannerView.findViewById(R.id.tv__ads);
|
||||
//TODO: pass as constructor arguments
|
||||
mAdsLoader = new FacebookAdsLoader();
|
||||
mAdsLoader.setAdsListener(new NativeAdsListener());
|
||||
}
|
||||
|
||||
private void setErrorStatus(boolean value)
|
||||
|
@ -111,6 +112,7 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
&& mAdsLoader.isCacheEmptyForId(mBanner.getId()))
|
||||
{
|
||||
UiUtils.hide(mIcon, mTitle, mMessage, mActionSmall, mActionLarge, mAds);
|
||||
onChangedVisibility(mBanner, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -124,6 +126,9 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
|
||||
void updateData(@Nullable Banner banner)
|
||||
{
|
||||
if (mBanner != null && !mBanner.equals(banner))
|
||||
onChangedVisibility(mBanner, false);
|
||||
|
||||
UiUtils.hide(mFrame);
|
||||
setErrorStatus(false);
|
||||
|
||||
|
@ -136,7 +141,9 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
|
||||
UiUtils.show(mFrame);
|
||||
|
||||
NativeAd data = mAdsLoader.load(mFrame.getContext(), mBanner.getId(), this);
|
||||
NativeAd data = mAdsLoader.load(mFrame.getContext(), mBanner.getId());
|
||||
updateVisibility();
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
LOGGER.d(TAG, "A cached ad '" + mBanner + "' is shown");
|
||||
|
@ -145,8 +152,6 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
loadIconAndOpenIfNeeded(data, mBanner);
|
||||
}
|
||||
|
||||
updateVisibility();
|
||||
|
||||
if (mOpened && mListener != null)
|
||||
mListener.onSizeChanged();
|
||||
}
|
||||
|
@ -206,33 +211,14 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
NativeAd.downloadAndDisplayImage(icon, mIcon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Ad ad, @NonNull AdError adError, boolean isCacheEmpty)
|
||||
private void onChangedVisibility(@NonNull Banner banner, boolean isVisible)
|
||||
{
|
||||
setErrorStatus(isCacheEmpty);
|
||||
updateVisibility();
|
||||
|
||||
if (mListener != null && isCacheEmpty)
|
||||
mListener.onSizeChanged();
|
||||
Statistics.INSTANCE.trackFacebookBannerError(mBanner, adError, mOpened ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFacebookAdLoaded(@NonNull NativeAd ad)
|
||||
void onChangedVisibility(boolean isVisible)
|
||||
{
|
||||
if (mBanner == null)
|
||||
return;
|
||||
|
||||
updateVisibility();
|
||||
|
||||
fillViews(ad);
|
||||
|
||||
registerViewsForInteraction(ad);
|
||||
|
||||
loadIconAndOpenIfNeeded(ad, mBanner);
|
||||
|
||||
if (mListener != null && mOpened)
|
||||
mListener.onSizeChanged();
|
||||
if (mBanner != null)
|
||||
onChangedVisibility(mBanner, isVisible);
|
||||
}
|
||||
|
||||
private void registerViewsForInteraction(@NonNull NativeAd ad)
|
||||
|
@ -272,15 +258,6 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdClicked(Ad ad)
|
||||
{
|
||||
if (mBanner == null)
|
||||
return;
|
||||
|
||||
Statistics.INSTANCE.trackFacebookBanner(PP_BANNER_CLICK, mBanner, mOpened ? 1 : 0);
|
||||
}
|
||||
|
||||
boolean isActionButtonTouched(@NonNull MotionEvent event)
|
||||
{
|
||||
return isTouched(mActionSmall, event) || isTouched(mActionLarge, event)
|
||||
|
@ -291,4 +268,45 @@ final class BannerController extends FacebookAdsLoader.FacebookAdsListener
|
|||
{
|
||||
void onSizeChanged();
|
||||
}
|
||||
|
||||
private class NativeAdsListener implements FacebookAdsLoader.FacebookAdsListener
|
||||
{
|
||||
@Override
|
||||
public void onAdClicked(@NonNull Ad ad)
|
||||
{
|
||||
if (mBanner == null)
|
||||
return;
|
||||
|
||||
Statistics.INSTANCE.trackFacebookBanner(PP_BANNER_CLICK, mBanner, mOpened ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Ad ad, @NonNull AdError adError, boolean isCacheEmpty)
|
||||
{
|
||||
setErrorStatus(isCacheEmpty);
|
||||
updateVisibility();
|
||||
|
||||
if (mListener != null && isCacheEmpty)
|
||||
mListener.onSizeChanged();
|
||||
Statistics.INSTANCE.trackFacebookBannerError(mBanner, adError, mOpened ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFacebookAdLoaded(@NonNull NativeAd ad)
|
||||
{
|
||||
if (mBanner == null)
|
||||
return;
|
||||
|
||||
updateVisibility();
|
||||
|
||||
fillViews(ad);
|
||||
|
||||
registerViewsForInteraction(ad);
|
||||
|
||||
loadIconAndOpenIfNeeded(ad, mBanner);
|
||||
|
||||
if (mListener != null && mOpened)
|
||||
mListener.onSizeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,6 +281,19 @@ public class PlacePageView extends RelativeLayout
|
|||
return UiUtils.isViewTouched(event, mHotelGallery);
|
||||
}
|
||||
|
||||
public void onActivityResume()
|
||||
{
|
||||
if (mBannerController != null)
|
||||
mBannerController.onChangedVisibility(true);
|
||||
|
||||
}
|
||||
|
||||
public void onActivityPause()
|
||||
{
|
||||
if (mBannerController != null)
|
||||
mBannerController.onChangedVisibility(false);
|
||||
}
|
||||
|
||||
private void initViews()
|
||||
{
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.place_page, this);
|
||||
|
@ -1431,7 +1444,7 @@ public class PlacePageView extends RelativeLayout
|
|||
|
||||
public void setOnVisibilityChangedListener(BasePlacePageAnimationController.OnVisibilityChangedListener listener)
|
||||
{
|
||||
mAnimationController.setOnVisibilityChangedListener(listener);
|
||||
mAnimationController.setOnVisibilityChangedListener(new PlacePageVisibilityProxy(listener, mBannerController));
|
||||
}
|
||||
|
||||
public void setOnAnimationListener(@Nullable BasePlacePageAnimationController.OnAnimationListener listener)
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.mapswithme.maps.widget.placepage;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
||||
public class PlacePageVisibilityProxy implements BasePlacePageAnimationController.OnVisibilityChangedListener
|
||||
{
|
||||
@NonNull
|
||||
private final BasePlacePageAnimationController.OnVisibilityChangedListener mListener;
|
||||
@Nullable
|
||||
private final BannerController mBannerController;
|
||||
|
||||
public PlacePageVisibilityProxy(@NonNull BasePlacePageAnimationController.OnVisibilityChangedListener listener,
|
||||
@Nullable BannerController bannerController)
|
||||
{
|
||||
mListener = listener;
|
||||
mBannerController = bannerController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreviewVisibilityChanged(boolean isVisible)
|
||||
{
|
||||
if (mBannerController != null)
|
||||
mBannerController.onChangedVisibility(isVisible);
|
||||
mListener.onPreviewVisibilityChanged(isVisible);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlacePageVisibilityChanged(boolean isVisible)
|
||||
{
|
||||
mListener.onPlacePageVisibilityChanged(isVisible);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue