package com.mopub.mobileads;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MraidView extends WebView {
private static final String LOGTAG = "MraidView";
private MraidBrowserController mBrowserController;
private MraidDisplayController mDisplayController;
private WebViewClient mWebViewClient;
private WebChromeClient mWebChromeClient;
private boolean mHasFiredReadyEvent;
private final PlacementType mPlacementType;
static class MraidListenerInfo {
private OnExpandListener mOnExpandListener;
private OnCloseListener mOnCloseListener;
private OnReadyListener mOnReadyListener;
private OnFailureListener mOnFailureListener;
private OnCloseButtonStateChangeListener mOnCloseButtonListener;
private OnOpenListener mOnOpenListener;
}
private MraidListenerInfo mListenerInfo;
public enum ViewState {
LOADING,
DEFAULT,
EXPANDED,
HIDDEN
}
enum ExpansionStyle {
ENABLED,
DISABLED
}
enum NativeCloseButtonStyle {
ALWAYS_VISIBLE,
ALWAYS_HIDDEN,
AD_CONTROLLED
}
enum PlacementType {
INLINE,
INTERSTITIAL
}
public MraidView(Context context) {
this(context, ExpansionStyle.ENABLED, NativeCloseButtonStyle.AD_CONTROLLED,
PlacementType.INLINE);
}
MraidView(Context context, ExpansionStyle expStyle, NativeCloseButtonStyle buttonStyle,
PlacementType placementType) {
super(context);
mPlacementType = placementType;
initialize(expStyle, buttonStyle);
}
private void initialize(ExpansionStyle expStyle, NativeCloseButtonStyle buttonStyle) {
setScrollContainer(false);
setBackgroundColor(Color.TRANSPARENT);
setVerticalScrollBarEnabled(false);
setHorizontalScrollBarEnabled(false);
setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
if (!v.hasFocus()) {
v.requestFocus();
}
break;
}
return false;
}
});
getSettings().setJavaScriptEnabled(true);
mBrowserController = new MraidBrowserController(this);
mDisplayController = new MraidDisplayController(this, expStyle, buttonStyle);
mWebViewClient = new MraidWebViewClient();
setWebViewClient(mWebViewClient);
mWebChromeClient = new MraidWebChromeClient();
setWebChromeClient(mWebChromeClient);
mListenerInfo = new MraidListenerInfo();
}
public void destroy() {
mDisplayController.destroy();
super.destroy();
}
public void loadHtmlData(String data) {
// If the string data lacks the HTML boilerplate, add it.
if (data.indexOf("<html>") == -1) {
data = "<html><head></head><body style='margin:0;padding:0;'>" + data +
"</body></html>";
}
// Inject the MRAID JavaScript bridge.
String mraid = "file://" + copyRawResourceToFilesDir(R.raw.mraid, "mraid.js");
data = data.replace("<head>", "<head><script src='" + mraid + "'></script>");
loadDataWithBaseURL(null, data, "text/html", "UTF-8", null);
}
public void loadUrl(String url) {
HttpClient httpClient = new DefaultHttpClient();
StringBuffer out = new StringBuffer();
try {
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream is = entity.getContent();
byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) {
out.append(new String(b, 0, n));
}
}
} catch (IllegalArgumentException e) {
Log.d("MoPub", "Mraid loadUrl failed (IllegalArgumentException): "+url);
notifyOnFailureListener();
return;
} catch (ClientProtocolException e) {
notifyOnFailureListener();
return;
} catch (IOException e) {
notifyOnFailureListener();
return;
}
loadHtmlData(out.toString());
}
private void notifyOnFailureListener() {
if (mListenerInfo.mOnFailureListener != null) {
mListenerInfo.mOnFailureListener.onFailure(this);
}
}
// Controllers /////////////////////////////////////////////////////////////////////////////////
protected MraidBrowserController getBrowserController() {
return mBrowserController;
}
protected MraidDisplayController getDisplayController() {
return mDisplayController;
}
// Listeners ///////////////////////////////////////////////////////////////////////////////////
public void setOnExpandListener(OnExpandListener listener) {
mListenerInfo.mOnExpandListener = listener;
}
public OnExpandListener getOnExpandListener() {
return mListenerInfo.mOnExpandListener;
}
public void setOnCloseListener(OnCloseListener listener) {
mListenerInfo.mOnCloseListener = listener;
}
public OnCloseListener getOnCloseListener() {
return mListenerInfo.mOnCloseListener;
}
public void setOnReadyListener(OnReadyListener listener) {
mListenerInfo.mOnReadyListener = listener;
}
public OnReadyListener getOnReadyListener() {
return mListenerInfo.mOnReadyListener;
}
public void setOnFailureListener(OnFailureListener listener) {
mListenerInfo.mOnFailureListener = listener;
}
public OnFailureListener getOnFailureListener() {
return mListenerInfo.mOnFailureListener;
}
public void setOnCloseButtonStateChange(OnCloseButtonStateChangeListener listener) {
mListenerInfo.mOnCloseButtonListener = listener;
}
public OnCloseButtonStateChangeListener getOnCloseButtonStateChangeListener() {
return mListenerInfo.mOnCloseButtonListener;
}
public void setOnOpenListener(OnOpenListener listener) {
mListenerInfo.mOnOpenListener = listener;
}
public OnOpenListener getOnOpenListener() {
return mListenerInfo.mOnOpenListener;
}
// JavaScript injection ////////////////////////////////////////////////////////////////////////
protected void injectJavaScript(String js) {
if (js != null) super.loadUrl("javascript:" + js);
}
protected void fireChangeEventForProperty(MraidProperty property) {
String json = "{" + property.toString() + "}";
injectJavaScript("window.mraidbridge.fireChangeEvent(" + json + ");");
Log.d(LOGTAG, "Fire change: " + json);
}
protected void fireChangeEventForProperties(ArrayList<MraidProperty> properties) {
String props = properties.toString();
if (props.length() < 2) return;
String json = "{" + props.substring(1, props.length() - 1) + "}";
injectJavaScript("window.mraidbridge.fireChangeEvent(" + json + ");");
Log.d(LOGTAG, "Fire changes: " + json);
}
protected void fireErrorEvent(String action, String message) {
injectJavaScript("window.mraidbridge.fireErrorEvent('" + action + "', '" + message + "');");
}
protected void fireReadyEvent() {
injectJavaScript("window.mraidbridge.fireReadyEvent();");
}
protected void fireNativeCommandCompleteEvent(String command) {
injectJavaScript("window.mraidbridge.nativeCallComplete('" + command + "');");
}
private boolean tryCommand(URI uri) {
String commandType = uri.getHost();
List<NameValuePair> list = URLEncodedUtils.parse(uri, "UTF-8");
Map<String, String> params = new HashMap<String, String>();
for (NameValuePair pair : list) {
params.put(pair.getName(), pair.getValue());
}
MraidCommand command = MraidCommandRegistry.createCommand(commandType, params, this);
if (command == null) {
fireNativeCommandCompleteEvent(commandType);
return false;
} else {
command.execute();
fireNativeCommandCompleteEvent(commandType);
return true;
}
}
/*
* Copies a file from res/raw to <destinationFilename> in the application file directory.
*/
private String copyRawResourceToFilesDir(int resourceId, String destinationFilename) {
InputStream is = getContext().getResources().openRawResource(resourceId);
String destinationPath = getContext().getFilesDir().getAbsolutePath() + File.separator +
destinationFilename;
File destinationFile = new File(destinationPath);
FileOutputStream fos;
try {
fos = new FileOutputStream(destinationFile);
} catch (FileNotFoundException e) {
return "";
}
byte[] b = new byte[8192];
try {
for (int n; (n = is.read(b)) != -1;) {
fos.write(b, 0, n);
}
} catch (IOException e) {
return "";
} finally {
try { is.close(); fos.close(); } catch (IOException e) { }
}
return destinationPath;
}
private class MraidWebViewClient extends WebViewClient {
@Override
public void onReceivedError(WebView view, int errorCode, String description,
String failingUrl) {
Log.d(LOGTAG, "Error: " + description);
super.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if (scheme.equals("mopub")) return true;
if (scheme.equals("mraid")) {
tryCommand(URI.create(url)); // java.net.URI, not android.net.Uri
return true;
}
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
getContext().startActivity(i);
return true;
} catch (ActivityNotFoundException e) {
return false;
}
}
@Override
public void onPageFinished(WebView view, String url) {
if (!mHasFiredReadyEvent) {
mDisplayController.initializeJavaScriptState();
fireChangeEventForProperty(
MraidPlacementTypeProperty.createWithType(mPlacementType));
fireReadyEvent();
if (getOnReadyListener() != null) getOnReadyListener().onReady(MraidView.this);
mHasFiredReadyEvent = true;
}
}
@Override
public void onLoadResource(WebView view, String url) {
Log.d(LOGTAG, "Loaded resource: " + url);
}
}
private class MraidWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d(LOGTAG, message);
return false;
}
}
public interface OnExpandListener {
public void onExpand(MraidView view);
}
public interface OnCloseListener {
public void onClose(MraidView view, ViewState newViewState);
}
public interface OnReadyListener {
public void onReady(MraidView view);
}
public interface OnFailureListener {
public void onFailure(MraidView view);
}
public interface OnCloseButtonStateChangeListener {
public void onCloseButtonStateChange(MraidView view, boolean enabled);
}
public interface OnOpenListener {
public void onOpen(MraidView view);
}
}