package com.mopub.network; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import com.mopub.common.AdFormat; import com.mopub.common.AdType; import com.mopub.common.DataKeys; import com.mopub.common.util.Json; import com.mopub.common.util.ResponseHeader; import com.mopub.mobileads.AdTypeTranslator; import com.mopub.volley.DefaultRetryPolicy; import com.mopub.volley.NetworkResponse; import com.mopub.volley.Request; import com.mopub.volley.Response; import com.mopub.volley.toolbox.HttpHeaderParser; import org.json.JSONException; import org.json.JSONObject; import java.io.*; import java.util.*; import static com.mopub.network.HeaderUtils.extractBooleanHeader; import static com.mopub.network.HeaderUtils.extractHeader; import static com.mopub.network.HeaderUtils.extractIntegerHeader; public class AdRequest extends Request<AdResponse> { @NonNull private final AdRequest.Listener mListener; @NonNull private final AdFormat mAdFormat; public interface Listener extends Response.ErrorListener { public void onSuccess(AdResponse response); } public AdRequest(@NonNull final String url, @NonNull final AdFormat adFormat, @NonNull final Listener listener) { super(Method.GET, url, listener); mListener = listener; mAdFormat = adFormat; DefaultRetryPolicy retryPolicy = new DefaultRetryPolicy( DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT); setRetryPolicy(retryPolicy); setShouldCache(false); } @Override protected Response<AdResponse> parseNetworkResponse(final NetworkResponse networkResponse) { // NOTE: We never get status codes outside of {[200, 299], 304}. Those errors are sent to the // error listener. Map<String, String> headers = networkResponse.headers; if (extractBooleanHeader(headers, ResponseHeader.WARMUP, false)) { return Response.error(new MoPubNetworkError("Ad Unit is warming up.", MoPubNetworkError.Reason.WARMING_UP)); } AdResponse.Builder builder = new AdResponse.Builder(); String adTypeString = extractHeader(headers, ResponseHeader.AD_TYPE); String fullAdTypeString = extractHeader(headers, ResponseHeader.FULL_AD_TYPE); builder.setAdType(adTypeString); builder.setFullAdType(fullAdTypeString); if (AdType.CLEAR.equals(adTypeString)) { return Response.error(new MoPubNetworkError("No ads found for ad unit.", MoPubNetworkError.Reason.NO_FILL)); } builder.setNetworkType(extractHeader(headers, ResponseHeader.NETWORK_TYPE)); String redirectUrl = extractHeader(headers, ResponseHeader.REDIRECT_URL); builder.setRedirectUrl(redirectUrl); String clickTrackingUrl = extractHeader(headers, ResponseHeader.CLICK_TRACKING_URL); builder.setClickTrackingUrl(clickTrackingUrl); builder.setImpressionTrackingUrl(extractHeader(headers, ResponseHeader.IMPRESSION_URL)); builder.setFailoverUrl(extractHeader(headers, ResponseHeader.FAIL_URL)); boolean isScrollable = extractBooleanHeader(headers, ResponseHeader.SCROLLABLE, false); builder.setScrollable(isScrollable); builder.setDimensions(extractIntegerHeader(headers, ResponseHeader.WIDTH), extractIntegerHeader(headers, ResponseHeader.HEIGHT)); Integer adTimeoutDelaySeconds = extractIntegerHeader(headers, ResponseHeader.AD_TIMEOUT); builder.setAdTimeoutDelayMilliseconds( adTimeoutDelaySeconds == null ? null : adTimeoutDelaySeconds * 1000); Integer refreshTimeSeconds = extractIntegerHeader(headers, ResponseHeader.REFRESH_TIME); builder.setRefreshTimeMilliseconds( refreshTimeSeconds == null ? null : refreshTimeSeconds * 1000); // Response Body encoding / decoding String responseBody = parseStringBody(networkResponse); builder.setResponseBody(responseBody); if (AdType.NATIVE.equals(adTypeString)) { try { builder.setJsonBody(new JSONObject(responseBody)); } catch (JSONException e) { return Response.error( new MoPubNetworkError("Failed to decode body JSON for native ad format", e, MoPubNetworkError.Reason.BAD_BODY)); } } // Derive custom event fields String customEventClassName = AdTypeTranslator.getCustomEventName(mAdFormat, adTypeString, fullAdTypeString, headers); builder.setCustomEventClassName(customEventClassName); // Process server extras if they are present: String customEventData = extractHeader(headers, ResponseHeader.CUSTOM_EVENT_DATA); // Some server-supported custom events (like Millennial banners) use a different header field if (TextUtils.isEmpty(customEventData)) { customEventData = extractHeader(headers, ResponseHeader.NATIVE_PARAMS); } try { builder.setServerExtras(Json.jsonStringToMap(customEventData)); } catch (JSONException e) { return Response.error( new MoPubNetworkError("Failed to decode server extras for custom event data.", e, MoPubNetworkError.Reason.BAD_HEADER_DATA)); } // Some MoPub-specific custom events get their serverExtras from the response itself: if (eventDataIsInResponseBody(adTypeString, fullAdTypeString)) { Map<String, String> eventDataMap = new TreeMap<String, String>(); eventDataMap.put(DataKeys.HTML_RESPONSE_BODY_KEY, responseBody); eventDataMap.put(DataKeys.SCROLLABLE_KEY, Boolean.toString(isScrollable)); if (redirectUrl != null) { eventDataMap.put(DataKeys.REDIRECT_URL_KEY, redirectUrl); } if (clickTrackingUrl != null) { eventDataMap.put(DataKeys.CLICKTHROUGH_URL_KEY, clickTrackingUrl); } builder.setServerExtras(eventDataMap); } return Response.success(builder.build(), // Cast needed for Response generic. HttpHeaderParser.parseCacheHeaders(networkResponse)); } private boolean eventDataIsInResponseBody(@Nullable String adType, @Nullable String fullAdType) { return "mraid".equals(adType) || "html".equals(adType) || ("interstitial".equals(adType) && "vast".equals(fullAdType)); } // Based on Volley's StringResponse class. protected String parseStringBody(NetworkResponse response) { String parsed; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return parsed; } @Override protected void deliverResponse(final AdResponse adResponse) { mListener.onSuccess(adResponse); } }