forked from organicmaps/organicmaps
[android] Added the ad lifecycle tracker
This commit is contained in:
parent
2e0077ac23
commit
b7650c297c
5 changed files with 164 additions and 28 deletions
|
@ -2,8 +2,10 @@ package com.mapswithme.maps.ads;
|
|||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
interface AdTracker
|
||||
public interface AdTracker
|
||||
{
|
||||
void start(@NonNull String bannerId);
|
||||
void stop(@NonNull String bannerId);
|
||||
void onViewShown(@NonNull String bannerId);
|
||||
void onViewHidden(@NonNull String bannerId);
|
||||
void onContentObtained(@NonNull String bannerId);
|
||||
boolean isImpressionGood(@NonNull String bannerId);
|
||||
}
|
||||
|
|
125
android/src/com/mapswithme/maps/ads/DefaultAdTracker.java
Normal file
125
android/src/com/mapswithme/maps/ads/DefaultAdTracker.java
Normal file
|
@ -0,0 +1,125 @@
|
|||
package com.mapswithme.maps.ads;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.mapswithme.util.log.Logger;
|
||||
import com.mapswithme.util.log.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultAdTracker implements AdTracker, OnAdCacheModifiedListener
|
||||
{
|
||||
private final static Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
|
||||
private final static String TAG = DefaultAdTracker.class.getSimpleName();
|
||||
private final static int GOOD_IMPRESSION_TIME_MS = 2000;
|
||||
private final Map<String, TrackInfo> mTracks = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onViewShown(@NonNull String bannerId)
|
||||
{
|
||||
LOGGER.d(TAG, "onViewShown bannerId = " + bannerId);
|
||||
TrackInfo info = mTracks.get(bannerId);
|
||||
if (info != null)
|
||||
info.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewHidden(@NonNull String bannerId)
|
||||
{
|
||||
LOGGER.d(TAG, "onViewHidden bannerId = " + bannerId);
|
||||
TrackInfo info = mTracks.get(bannerId);
|
||||
if (info != null)
|
||||
info.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentObtained(@NonNull String bannerId)
|
||||
{
|
||||
LOGGER.d(TAG, "onContentObtained bannerId = " + bannerId);
|
||||
TrackInfo info = mTracks.get(bannerId);
|
||||
if (info == null)
|
||||
throw new AssertionError("A track info must be put in a cache before a content is obtained");
|
||||
|
||||
info.fill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImpressionGood(@NonNull String bannerId)
|
||||
{
|
||||
TrackInfo info = mTracks.get(bannerId);
|
||||
return info != null && info.getShowTime() > GOOD_IMPRESSION_TIME_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(@NonNull String id)
|
||||
{
|
||||
LOGGER.d(TAG, "onRemoved id = " + id);
|
||||
mTracks.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPut(@NonNull String id)
|
||||
{
|
||||
LOGGER.d(TAG, "onPut id = " + id);
|
||||
mTracks.put(id, new TrackInfo());
|
||||
}
|
||||
|
||||
private static class TrackInfo
|
||||
{
|
||||
private long mTimestamp;
|
||||
/**
|
||||
* Accumulates amount of time that ad is already shown.
|
||||
*/
|
||||
private long mShowTime;
|
||||
/**
|
||||
* Indicates whether the ad view is visible or not.
|
||||
*/
|
||||
private boolean mVisible;
|
||||
/**
|
||||
* Indicates whether the ad content is obtained or not.
|
||||
*/
|
||||
private boolean mFilled;
|
||||
|
||||
public void setVisible(boolean visible)
|
||||
{
|
||||
// No need tracking if the ad is not filled with a content
|
||||
if (!mFilled)
|
||||
return;
|
||||
|
||||
// If ad becomes visible, and it's filled with a content the timestamp must be stored.
|
||||
if (visible && !mVisible)
|
||||
{
|
||||
mTimestamp = SystemClock.elapsedRealtime();
|
||||
}
|
||||
// If ad is hidden the show time must be accumulated.
|
||||
else if (!visible && mVisible)
|
||||
{
|
||||
mShowTime += SystemClock.elapsedRealtime() - mTimestamp;
|
||||
mTimestamp = 0;
|
||||
}
|
||||
mVisible = visible;
|
||||
}
|
||||
|
||||
public void fill()
|
||||
{
|
||||
// If the visible ad is filled with the content the timestamp must be stored
|
||||
if (mVisible)
|
||||
{
|
||||
mTimestamp = SystemClock.elapsedRealtime();
|
||||
}
|
||||
mFilled = true;
|
||||
}
|
||||
|
||||
private boolean isRealShow()
|
||||
{
|
||||
return mFilled && mVisible;
|
||||
}
|
||||
|
||||
long getShowTime()
|
||||
{
|
||||
return mShowTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,16 +35,19 @@ public class FacebookAdsLoader implements AdListener
|
|||
|
||||
/**
|
||||
* 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 {@link #mAdsListener} will
|
||||
* be notified when the requested ad is loaded.
|
||||
* the caller will be notified immediately through {@link FacebookAdsListener#onFacebookAdLoaded(NativeAd)}.
|
||||
* Otherwise, the caller will be notified once an ad is loaded through the mentioned method.
|
||||
*
|
||||
* <br><br><b>Important note: </b> if there is a cached ad for the requested placement 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.
|
||||
*
|
||||
* @param context An activity context.
|
||||
* @param placementId A placement id that ad will be loaded for.
|
||||
* @return A cached banner if it presents, otherwise <code>null</code>
|
||||
* @param tracker An ad tracker
|
||||
*/
|
||||
@Nullable
|
||||
@UiThread
|
||||
public NativeAd load(@NonNull Context context, @NonNull String placementId)
|
||||
public void load(@NonNull Context context, @NonNull String placementId, @NonNull AdTracker tracker)
|
||||
{
|
||||
LOGGER.d(TAG, "Load a facebook ad for a placement id '" + placementId + "'");
|
||||
|
||||
|
@ -54,17 +57,18 @@ public class FacebookAdsLoader implements AdListener
|
|||
{
|
||||
LOGGER.d(TAG, "There is no an ad in a cache");
|
||||
loadAdInternal(context, placementId);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (/** Tracker.checkShowTime(placmenetId) &&**/
|
||||
SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS)
|
||||
if (tracker.isImpressionGood(placementId)
|
||||
&& SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS)
|
||||
{
|
||||
LOGGER.d(TAG, "Ad should be reloaded");
|
||||
LOGGER.d(TAG, "A new ad will be loaded because the previous one has a good impression");
|
||||
loadAdInternal(context, placementId);
|
||||
}
|
||||
|
||||
return cachedAd.getAd();
|
||||
if (mAdsListener != null)
|
||||
mAdsListener.onFacebookAdLoaded(cachedAd.getAd());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,7 +172,7 @@ public class FacebookAdsLoader implements AdListener
|
|||
{
|
||||
mCache.remove(key);
|
||||
if (mCacheListener != null)
|
||||
mCacheListener.onRemove(key);
|
||||
mCacheListener.onRemoved(key);
|
||||
}
|
||||
|
||||
public interface FacebookAdsListener
|
||||
|
|
|
@ -4,6 +4,6 @@ import android.support.annotation.NonNull;
|
|||
|
||||
interface OnAdCacheModifiedListener
|
||||
{
|
||||
void onRemove(@NonNull String id);
|
||||
void onRemoved(@NonNull String id);
|
||||
void onPut(@NonNull String id);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.facebook.ads.Ad;
|
|||
import com.facebook.ads.AdError;
|
||||
import com.facebook.ads.NativeAd;
|
||||
import com.mapswithme.maps.R;
|
||||
import com.mapswithme.maps.ads.DefaultAdTracker;
|
||||
import com.mapswithme.maps.ads.FacebookAdsLoader;
|
||||
import com.mapswithme.maps.bookmarks.data.Banner;
|
||||
import com.mapswithme.util.Config;
|
||||
|
@ -74,6 +75,8 @@ final class BannerController
|
|||
|
||||
@NonNull
|
||||
private final FacebookAdsLoader mAdsLoader;
|
||||
@NonNull
|
||||
private final DefaultAdTracker mAdTracker;
|
||||
|
||||
BannerController(@NonNull View bannerView, @Nullable BannerListener listener)
|
||||
{
|
||||
|
@ -90,6 +93,8 @@ final class BannerController
|
|||
//TODO: pass as constructor arguments
|
||||
mAdsLoader = new FacebookAdsLoader();
|
||||
mAdsLoader.setAdsListener(new NativeAdsListener());
|
||||
mAdTracker = new DefaultAdTracker();
|
||||
mAdsLoader.setCacheListener(mAdTracker);
|
||||
}
|
||||
|
||||
private void setErrorStatus(boolean value)
|
||||
|
@ -141,19 +146,7 @@ final class BannerController
|
|||
|
||||
UiUtils.show(mFrame);
|
||||
|
||||
NativeAd data = mAdsLoader.load(mFrame.getContext(), mBanner.getId());
|
||||
updateVisibility();
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
LOGGER.d(TAG, "A cached ad '" + mBanner + "' is shown");
|
||||
fillViews(data);
|
||||
registerViewsForInteraction(data);
|
||||
loadIconAndOpenIfNeeded(data, mBanner);
|
||||
}
|
||||
|
||||
if (mOpened && mListener != null)
|
||||
mListener.onSizeChanged();
|
||||
mAdsLoader.load(mFrame.getContext(), mBanner.getId(), mAdTracker);
|
||||
}
|
||||
|
||||
boolean isBannerVisible()
|
||||
|
@ -213,6 +206,16 @@ final class BannerController
|
|||
|
||||
private void onChangedVisibility(@NonNull Banner banner, boolean isVisible)
|
||||
{
|
||||
if (TextUtils.isEmpty(banner.getId()))
|
||||
{
|
||||
LOGGER.e(TAG, "Banner must have a non-null id!", new Throwable());
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible)
|
||||
mAdTracker.onViewShown(banner.getId());
|
||||
else
|
||||
mAdTracker.onViewHidden(banner.getId());
|
||||
}
|
||||
|
||||
void onChangedVisibility(boolean isVisible)
|
||||
|
@ -305,6 +308,8 @@ final class BannerController
|
|||
|
||||
loadIconAndOpenIfNeeded(ad, mBanner);
|
||||
|
||||
mAdTracker.onContentObtained(ad.getPlacementId());
|
||||
|
||||
if (mListener != null && mOpened)
|
||||
mListener.onSizeChanged();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue