package com.mopub.nativeads; import android.content.Context; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.mopub.common.Constants; import com.mopub.common.VisibleForTesting; import com.mopub.common.logging.MoPubLog; import com.mopub.nativeads.MoPubNativeAdPositioning.MoPubClientPositioning; import com.mopub.network.MoPubNetworkError; import com.mopub.network.Networking; import com.mopub.volley.RequestQueue; import com.mopub.volley.Response; import com.mopub.volley.VolleyError; /** * Requests positioning information from the MoPub ad server. * * The expected JSON format contains a set of rules for fixed and repeating positions. For example: * { * fixed: [{ * position: 7 * }, { * section : 1 * position: 6 * }], * repeating: { * interval: 12 * } * } * * Both fixed and repeating rules are optional. If they exist they must follow the following * guidelines: * * fixed - contains a set of positioning objects, each with an optional section and a required * position. Section is used for iOS clients only, and non-zero sections are ignored on Android. * * repeating - contains a required interval, which must be 2 or greater. * * The JSON parsing logic treats any violations to the above spec as invalid, * rather than trying to continue with a partially valid response. */ class ServerPositioningSource implements PositioningSource { private static final double DEFAULT_RETRY_TIME_MILLISECONDS = 1000; // 1 second private static final double EXPONENTIAL_BACKOFF_FACTOR = 2; @VisibleForTesting static int MAXIMUM_RETRY_TIME_MILLISECONDS = 5 * 60 * 1000; // 5 minutes. @NonNull private final Context mContext; // Handler and runnable for retrying after a failed response. @NonNull private final Handler mRetryHandler; @NonNull private final Runnable mRetryRunnable; private final Response.Listener<MoPubClientPositioning> mPositioningListener; private final Response.ErrorListener mErrorListener; @Nullable private PositioningListener mListener; private int mRetryCount; @Nullable private String mRetryUrl; @Nullable private PositioningRequest mRequest; ServerPositioningSource(@NonNull final Context context) { mContext = context.getApplicationContext(); mRetryHandler = new Handler(); mRetryRunnable = new Runnable() { @Override public void run() { requestPositioningInternal(); } }; mPositioningListener = new Response.Listener<MoPubClientPositioning>() { @Override public void onResponse(final MoPubClientPositioning clientPositioning) { handleSuccess(clientPositioning); } }; mErrorListener = new Response.ErrorListener() { @Override public void onErrorResponse(final VolleyError error) { // Don't log a stack trace when we're just warming up. if (!(error instanceof MoPubNetworkError) || ((MoPubNetworkError) error).getReason().equals(MoPubNetworkError.Reason.WARMING_UP)) { MoPubLog.e("Failed to load positioning data", error); } handleFailure(); } }; } @Override public void loadPositions(@NonNull String adUnitId, @NonNull PositioningListener listener) { // If a request is in flight, remove it. if (mRequest != null) { mRequest.cancel(); mRequest = null; } // If a retry is pending remove it. if (mRetryCount > 0) { mRetryHandler.removeCallbacks(mRetryRunnable); mRetryCount = 0; } mListener = listener; mRetryUrl = new PositioningUrlGenerator(mContext) .withAdUnitId(adUnitId) .generateUrlString(Constants.HOST); requestPositioningInternal(); } private void requestPositioningInternal() { MoPubLog.d("Loading positioning from: " + mRetryUrl); mRequest = new PositioningRequest(mRetryUrl, mPositioningListener, mErrorListener); final RequestQueue requestQueue = Networking.getRequestQueue(mContext); requestQueue.add(mRequest); } private void handleSuccess(@NonNull MoPubClientPositioning positioning) { if (mListener != null) { mListener.onLoad(positioning); } mListener = null; mRetryCount = 0; } private void handleFailure() { double multiplier = Math.pow(EXPONENTIAL_BACKOFF_FACTOR, mRetryCount + 1); int delay = (int) (DEFAULT_RETRY_TIME_MILLISECONDS * multiplier); if (delay >= MAXIMUM_RETRY_TIME_MILLISECONDS) { MoPubLog.d("Error downloading positioning information"); if (mListener != null) { mListener.onFailed(); } mListener = null; return; } mRetryCount++; mRetryHandler.postDelayed(mRetryRunnable, delay); } }