package com.mopub.nativeads; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.mopub.common.event.ErrorEvent; import com.mopub.common.event.MoPubEvents; import com.mopub.common.logging.MoPubLog; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.*; import static com.mopub.common.DataKeys.JSON_BODY_KEY; import static com.mopub.common.util.Numbers.parseDouble; import static com.mopub.nativeads.NativeResponse.Parameter; public class MoPubCustomEventNative extends CustomEventNative { @Override protected void loadNativeAd(@NonNull final Context context, @NonNull final CustomEventNativeListener customEventNativeListener, @NonNull final Map<String, Object> localExtras, @NonNull final Map<String, String> serverExtras) { Object json = localExtras.get(JSON_BODY_KEY); // null or non-JSONObjects should not be passed in localExtras as JSON_BODY_KEY if (!(json instanceof JSONObject)) { customEventNativeListener.onNativeAdFailed(NativeErrorCode.INVALID_JSON); return; } final MoPubForwardingNativeAd moPubForwardingNativeAd = new MoPubForwardingNativeAd(context.getApplicationContext(), (JSONObject) json, customEventNativeListener); try { moPubForwardingNativeAd.loadAd(); } catch (IllegalArgumentException e) { customEventNativeListener.onNativeAdFailed(NativeErrorCode.UNSPECIFIED); MoPubEvents.log(new ErrorEvent.Builder("","").withException(e).build()); } } static class MoPubForwardingNativeAd extends BaseForwardingNativeAd { @NonNull private final Context mContext; @NonNull private final CustomEventNativeListener mCustomEventNativeListener; @NonNull private final JSONObject mJsonObject; MoPubForwardingNativeAd(@NonNull final Context context, @NonNull final JSONObject jsonBody, @NonNull final CustomEventNativeListener customEventNativeListener) { mJsonObject = jsonBody; mContext = context; mCustomEventNativeListener = customEventNativeListener; } void loadAd() throws IllegalArgumentException { if (!containsRequiredKeys(mJsonObject)) { throw new IllegalArgumentException("JSONObject did not contain required keys."); } final Iterator<String> keys = mJsonObject.keys(); while (keys.hasNext()) { final String key = keys.next(); final Parameter parameter = Parameter.from(key); if (parameter != null) { try { addInstanceVariable(parameter, mJsonObject.opt(key)); } catch (ClassCastException e) { throw new IllegalArgumentException("JSONObject key (" + key + ") contained unexpected value."); } } else { addExtra(key, mJsonObject.opt(key)); } } preCacheImages(mContext, getAllImageUrls(), new ImageListener() { @Override public void onImagesCached() { mCustomEventNativeListener.onNativeAdLoaded(MoPubForwardingNativeAd.this); } @Override public void onImagesFailedToCache(final NativeErrorCode errorCode) { mCustomEventNativeListener.onNativeAdFailed(errorCode); } }); } private boolean containsRequiredKeys(@NonNull final JSONObject jsonObject) { final Set<String> keys = new HashSet<String>(); final Iterator<String> jsonKeys = jsonObject.keys(); while (jsonKeys.hasNext()) { keys.add(jsonKeys.next()); } return keys.containsAll(Parameter.requiredKeys); } private void addInstanceVariable(@NonNull final Parameter key, @Nullable final Object value) throws ClassCastException { try { switch (key) { case MAIN_IMAGE: setMainImageUrl((String) value); break; case ICON_IMAGE: setIconImageUrl((String) value); break; case IMPRESSION_TRACKER: addImpressionTrackers(value); break; case CLICK_TRACKER: break; case CLICK_DESTINATION: setClickDestinationUrl((String) value); break; case CALL_TO_ACTION: setCallToAction((String) value); break; case TITLE: setTitle((String) value); break; case TEXT: setText((String) value); break; case STAR_RATING: setStarRating(parseDouble(value)); break; default: MoPubLog.d("Unable to add JSON key to internal mapping: " + key.name); break; } } catch (ClassCastException e) { if (!key.required) { MoPubLog.d("Ignoring class cast exception for optional key: " + key.name); } else { throw e; } } } private void addImpressionTrackers(final Object impressionTrackers) throws ClassCastException { if (!(impressionTrackers instanceof JSONArray)) { throw new ClassCastException("Expected impression trackers of type JSONArray."); } final JSONArray trackers = (JSONArray) impressionTrackers; for (int i = 0; i < trackers.length(); i++) { try { addImpressionTracker(trackers.getString(i)); } catch (JSONException e) { // This will only occur if we access a non-existent index in JSONArray. MoPubLog.d("Unable to parse impression trackers."); } } } private boolean isImageKey(@Nullable final String name) { return name != null && name.toLowerCase(Locale.US).endsWith("image"); } @NonNull List<String> getExtrasImageUrls() { final List<String> extrasBitmapUrls = new ArrayList<String>(getExtras().size()); for (final Map.Entry<String, Object> entry : getExtras().entrySet()) { if (isImageKey(entry.getKey()) && entry.getValue() instanceof String) { extrasBitmapUrls.add((String) entry.getValue()); } } return extrasBitmapUrls; } @NonNull List<String> getAllImageUrls() { final List<String> imageUrls = new ArrayList<String>(); if (getMainImageUrl() != null) { imageUrls.add(getMainImageUrl()); } if (getIconImageUrl() != null) { imageUrls.add(getIconImageUrl()); } imageUrls.addAll(getExtrasImageUrls()); return imageUrls; } } }