package com.mopub.nativeads; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; import com.mopub.common.AdFormat; import com.mopub.common.Constants; import com.mopub.common.Preconditions; import com.mopub.common.VisibleForTesting; import com.mopub.common.logging.MoPubLog; import com.mopub.common.util.DeviceUtils; import com.mopub.common.util.ManifestUtils; import com.mopub.network.AdRequest; import com.mopub.network.AdResponse; import com.mopub.network.MoPubNetworkError; import com.mopub.network.Networking; import com.mopub.volley.NetworkResponse; import com.mopub.volley.RequestQueue; import com.mopub.volley.VolleyError; import java.lang.ref.WeakReference; import java.util.Map; import java.util.TreeMap; import static com.mopub.common.GpsHelper.fetchAdvertisingInfoAsync; import static com.mopub.nativeads.CustomEventNative.CustomEventNativeListener; import static com.mopub.nativeads.NativeErrorCode.CONNECTION_ERROR; import static com.mopub.nativeads.NativeErrorCode.EMPTY_AD_RESPONSE; import static com.mopub.nativeads.NativeErrorCode.INVALID_JSON; import static com.mopub.nativeads.NativeErrorCode.INVALID_REQUEST_URL; import static com.mopub.nativeads.NativeErrorCode.SERVER_ERROR_RESPONSE_CODE; import static com.mopub.nativeads.NativeErrorCode.UNSPECIFIED; public class MoPubNative { public interface MoPubNativeNetworkListener { public void onNativeLoad(final NativeResponse nativeResponse); public void onNativeFail(final NativeErrorCode errorCode); } static final MoPubNativeNetworkListener EMPTY_NETWORK_LISTENER = new MoPubNativeNetworkListener() { @Override public void onNativeLoad(@NonNull final NativeResponse nativeResponse) { // If this listener is invoked, it means that MoPubNative instance has been destroyed // so destroy any leftover incoming NativeResponses nativeResponse.destroy(); } @Override public void onNativeFail(final NativeErrorCode errorCode) { } }; static final MoPubNativeEventListener EMPTY_EVENT_LISTENER = new MoPubNativeEventListener() { @Override public void onNativeImpression(@Nullable final View view) { } @Override public void onNativeClick(@Nullable final View view) { } }; public interface MoPubNativeEventListener { public void onNativeImpression(final View view); public void onNativeClick(final View view); } /** * @deprecated As of release 2.4, use {@link MoPubNativeEventListener} and * {@link MoPubNativeNetworkListener} instead. */ @Deprecated public interface MoPubNativeListener extends MoPubNativeNetworkListener, MoPubNativeEventListener { } // must be an activity context since 3rd party networks need it @NonNull private final WeakReference<Context> mContext; @NonNull private final String mAdUnitId; @NonNull private MoPubNativeNetworkListener mMoPubNativeNetworkListener; @NonNull private MoPubNativeEventListener mMoPubNativeEventListener; // For small sets TreeMap, takes up less memory than HashMap @NonNull private Map<String, Object> mLocalExtras = new TreeMap<String, Object>(); @NonNull private final AdRequest.Listener mVolleyListener; @Nullable private AdRequest mNativeRequest; /** * @deprecated As of release 2.4, use {@link MoPubNative(Context, String, * MoPubNativeNetworkListener)} and {@link #setNativeEventListener(MoPubNativeEventListener)} * instead. */ @Deprecated public MoPubNative(@NonNull final Context context, @NonNull final String adUnitId, @NonNull final MoPubNativeListener moPubNativeListener) { this(context, adUnitId, (MoPubNativeNetworkListener) moPubNativeListener); setNativeEventListener(moPubNativeListener); } public MoPubNative(@NonNull final Context context, @NonNull final String adUnitId, @NonNull final MoPubNativeNetworkListener moPubNativeNetworkListener) { Preconditions.checkNotNull(context, "Context may not be null."); Preconditions.checkNotNull(adUnitId, "AdUnitId may not be null."); Preconditions.checkNotNull(moPubNativeNetworkListener, "MoPubNativeNetworkListener may not be null."); ManifestUtils.checkNativeActivitiesDeclared(context); mContext = new WeakReference<Context>(context); mAdUnitId = adUnitId; mMoPubNativeNetworkListener = moPubNativeNetworkListener; mMoPubNativeEventListener = EMPTY_EVENT_LISTENER; mVolleyListener = new AdRequest.Listener() { @Override public void onSuccess(@NonNull final AdResponse response) { onAdLoad(response); } @Override public void onErrorResponse(@NonNull final VolleyError volleyError) { onAdError(volleyError); } }; // warm up cache for google play services info fetchAdvertisingInfoAsync(context, null); } public void setNativeEventListener(@Nullable final MoPubNativeEventListener nativeEventListener) { mMoPubNativeEventListener = (nativeEventListener == null) ? EMPTY_EVENT_LISTENER : nativeEventListener; } public void destroy() { mContext.clear(); if (mNativeRequest != null) { mNativeRequest.cancel(); mNativeRequest = null; } mMoPubNativeNetworkListener = EMPTY_NETWORK_LISTENER; mMoPubNativeEventListener = EMPTY_EVENT_LISTENER; } public void setLocalExtras(@Nullable final Map<String, Object> localExtras) { if (localExtras == null) { mLocalExtras = new TreeMap<String, Object>(); } else { mLocalExtras = new TreeMap<String, Object>(localExtras); } } public void makeRequest() { makeRequest((RequestParameters)null); } public void makeRequest(@Nullable final RequestParameters requestParameters) { makeRequest(requestParameters, null); } public void makeRequest(@Nullable final RequestParameters requestParameters, @Nullable Integer sequenceNumber) { final Context context = getContextOrDestroy(); if (context == null) { return; } if (!DeviceUtils.isNetworkAvailable(context)) { mMoPubNativeNetworkListener.onNativeFail(CONNECTION_ERROR); return; } loadNativeAd(requestParameters, sequenceNumber); } private void loadNativeAd( @Nullable final RequestParameters requestParameters, @Nullable final Integer sequenceNumber) { final Context context = getContextOrDestroy(); if (context == null) { return; } final NativeUrlGenerator generator = new NativeUrlGenerator(context) .withAdUnitId(mAdUnitId) .withRequest(requestParameters); if (sequenceNumber != null) { generator.withSequenceNumber(sequenceNumber); } final String endpointUrl = generator.generateUrlString(Constants.HOST); if (endpointUrl != null) { MoPubLog.d("Loading ad from: " + endpointUrl); } requestNativeAd(endpointUrl); } void requestNativeAd(@Nullable final String endpointUrl) { final Context context = getContextOrDestroy(); if (context == null) { return; } if (endpointUrl == null) { mMoPubNativeNetworkListener.onNativeFail(INVALID_REQUEST_URL); return; } mNativeRequest = new AdRequest(endpointUrl, AdFormat.NATIVE, mVolleyListener); RequestQueue requestQueue = Networking.getRequestQueue(context); requestQueue.add(mNativeRequest); } private void onAdLoad(@NonNull final AdResponse response) { final Context context = getContextOrDestroy(); if (context == null) { return; } final CustomEventNativeListener customEventNativeListener = new CustomEventNativeListener() { @Override public void onNativeAdLoaded(@NonNull final NativeAdInterface nativeAd) { final Context context = getContextOrDestroy(); if (context == null) { return; } mMoPubNativeNetworkListener.onNativeLoad(new NativeResponse(context, response.getImpressionTrackingUrl(), response.getClickTrackingUrl(), mAdUnitId, nativeAd, mMoPubNativeEventListener)); } @Override public void onNativeAdFailed(final NativeErrorCode errorCode) { requestNativeAd(response.getFailoverUrl()); } }; CustomEventNativeAdapter.loadNativeAd( context, mLocalExtras, response, customEventNativeListener ); } @VisibleForTesting void onAdError(@NonNull final VolleyError volleyError) { MoPubLog.d("Native ad request failed.", volleyError); if (volleyError instanceof MoPubNetworkError) { MoPubNetworkError error = (MoPubNetworkError) volleyError; switch (error.getReason()) { case BAD_BODY: mMoPubNativeNetworkListener.onNativeFail(INVALID_JSON); return; case BAD_HEADER_DATA: mMoPubNativeNetworkListener.onNativeFail(INVALID_JSON); return; case WARMING_UP: case NO_FILL: mMoPubNativeNetworkListener.onNativeFail(EMPTY_AD_RESPONSE); return; case UNSPECIFIED: default: mMoPubNativeNetworkListener.onNativeFail(UNSPECIFIED); return; } } else { // Process our other status code errors. NetworkResponse response = volleyError.networkResponse; if (response != null && response.statusCode >= 500 && response.statusCode < 600) { mMoPubNativeNetworkListener.onNativeFail(SERVER_ERROR_RESPONSE_CODE); } else { mMoPubNativeNetworkListener.onNativeFail(UNSPECIFIED); } } } Context getContextOrDestroy() { final Context context = mContext.get(); if (context == null) { destroy(); MoPubLog.d("Weak reference to Activity Context in MoPubNative became null. This instance" + " of MoPubNative is destroyed and No more requests will be processed."); } return context; } @NonNull @VisibleForTesting @Deprecated MoPubNativeNetworkListener getMoPubNativeNetworkListener() { return mMoPubNativeNetworkListener; } @NonNull @VisibleForTesting @Deprecated MoPubNativeEventListener getMoPubNativeEventListener() { return mMoPubNativeEventListener; } }