br.com.support.SodaWebView.java Source code

Java tutorial

Introduction

Here is the source code for br.com.support.SodaWebView.java

Source

/*
 * Copyright (C) 2015 Luiz Gustavo Gesswein Cruz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package br.com.support;

import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Set;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebStorage.QuotaUpdater;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.GeolocationPermissions.Callback;

public class SodaWebView extends WebView {
    private SodaWebViewClient mSodaWebViewClient;
    private SodaWebViewBridge mSodaWebViewBridge;
    private SodaWebChromeClient mSodaWebChromeClient;

    private boolean debug = false;

    public SodaWebView(Context context) {
        super(context);
        init();
    }

    public SodaWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SodaWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {

        this.mSodaWebViewClient = new SodaWebViewClient(null);
        this.mSodaWebViewBridge = new SodaWebViewBridge();
        this.mSodaWebChromeClient = new SodaWebChromeClient();

        super.setWebViewClient(mSodaWebViewClient);
        super.setWebChromeClient(mSodaWebChromeClient);
    }

    @Override
    public void addJavascriptInterface(Object object, String name) {

        mSodaWebViewBridge.addInterface(object, name);

    }

    @Override
    public void setWebViewClient(WebViewClient client) {
        mSodaWebViewClient.setWebViewClient(client);
    }

    @Override
    public void setWebChromeClient(WebChromeClient client) {
        mSodaWebChromeClient.setWebChromeClient(client);
    }

    class SodaWebViewClient extends WebViewClient {

        private WebViewClient mWebViewClient;

        public SodaWebViewClient(WebViewClient webViewClient) {
            this.mWebViewClient = webViewClient;
        }

        @Override
        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
            if (this.mWebViewClient == null) {
                super.doUpdateVisitedHistory(view, url, isReload);
            } else {
                this.mWebViewClient.doUpdateVisitedHistory(view, url, isReload);
            }
        }

        @Override
        public void onFormResubmission(WebView view, Message dontResend, Message resend) {

            if (this.mWebViewClient == null) {
                super.onFormResubmission(view, dontResend, resend);
            } else {
                this.mWebViewClient.onFormResubmission(view, dontResend, resend);
            }
        }

        @Override
        public void onLoadResource(WebView view, String url) {

            if (this.mWebViewClient == null) {
                super.onLoadResource(view, url);
            } else {
                this.mWebViewClient.onLoadResource(view, url);
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {

            if (this.mWebViewClient == null) {
                super.onPageFinished(view, url);
            } else {
                this.mWebViewClient.onPageFinished(view, url);
            }
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            /*
             * Load JS bridge.
             */
            view.loadUrl("javascript:" + mSodaWebViewBridge.getBridge());

            /*
             * Load JS interfaces.
             */
            for (String interfaceName : mSodaWebViewBridge.getInterfaces()) {
                view.loadUrl("javascript:" + mSodaWebViewBridge.getInterfaceJS(interfaceName));
            }

            if (this.mWebViewClient == null) {
                super.onPageStarted(view, url, favicon);
            } else {
                this.mWebViewClient.onPageStarted(view, url, favicon);
            }
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

            if (this.mWebViewClient == null) {
                super.onReceivedError(view, errorCode, description, failingUrl);
            } else {
                this.mWebViewClient.onReceivedError(view, errorCode, description, failingUrl);
            }
        }

        @Override
        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {

            if (this.mWebViewClient == null) {
                super.onReceivedHttpAuthRequest(view, handler, host, realm);
            } else {
                this.mWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm);
            }
        }

        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
        @Override
        public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {

            if (this.mWebViewClient == null) {
                super.onReceivedLoginRequest(view, realm, account, args);
            } else {
                this.mWebViewClient.onReceivedLoginRequest(view, realm, account, args);
            }
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {

            if (this.mWebViewClient == null) {
                super.onReceivedSslError(view, handler, error);
            } else {
                this.mWebViewClient.onReceivedSslError(view, handler, error);
            }
        }

        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {

            if (this.mWebViewClient == null) {
                super.onScaleChanged(view, oldScale, newScale);
            } else {
                this.mWebViewClient.onScaleChanged(view, oldScale, newScale);
            }
        }

        @Override
        @Deprecated
        public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) {

            if (this.mWebViewClient == null) {
                super.onTooManyRedirects(view, cancelMsg, continueMsg);
            } else {
                this.mWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg);
            }
        }

        @Override
        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {

            if (this.mWebViewClient == null) {
                super.onUnhandledKeyEvent(view, event);
            } else {
                this.mWebViewClient.onUnhandledKeyEvent(view, event);
            }
        }

        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

            WebResourceResponse result = null;

            if (this.mWebViewClient == null) {
                result = super.shouldInterceptRequest(view, url);
            } else {
                result = this.mWebViewClient.shouldInterceptRequest(view, url);
            }
            return result;
        }

        @Override
        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {

            boolean result = false;

            if (this.mWebViewClient == null) {
                result = super.shouldOverrideKeyEvent(view, event);
            } else {
                result = this.mWebViewClient.shouldOverrideKeyEvent(view, event);
            }

            return result;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            boolean result = false;

            if (this.mWebViewClient == null) {
                result = super.shouldOverrideUrlLoading(view, url);
            } else {
                result = this.mWebViewClient.shouldOverrideUrlLoading(view, url);
            }
            return result;
        }

        public void setWebViewClient(WebViewClient webViewClient) {
            this.mWebViewClient = webViewClient;
        }

    };

    class SodaWebViewBridge {
        private HashMap<String, Object[]> mInterfaces = new HashMap<String, Object[]>();
        private static final String TAG = "SodaWebViewBridge";

        public SodaWebViewBridge() {

        }

        /**
         * Return a list name of mapped interfaces.
         * @return list.
         */
        public Set<String> getInterfaces() {
            return this.mInterfaces.keySet();
        }

        /**
         * Return a JavaScript string with functions based on object methods.
         * @param name interface name
         * @return a JavaScript string.
         */
        public String getInterfaceJS(String name) {
            return this.mInterfaces.get(name)[1].toString();
        }

        /**
         * Return a Java Object using the interface name.
         * @param name interface name
         * @return java Object.
         */
        public Object getInterfaceObject(String name) {
            return this.mInterfaces.get(name)[0];
        }

        /**
         * Add a interface to the SodaWebView and map an object to a JavaScript element.
         * @param object java Object to be mapped.
         * @param name interface name to be used with this object.
         */
        public void addInterface(Object object, String name) {
            String javaScript = mapObjectToJavaScript(object, name);

            this.mInterfaces.put(name, new Object[] { object, javaScript });
        }

        /**
         * Create the bridge that connects HTML to Soda Bridge.
         * @return a JavaScript string.
         */
        private String getBridge() {
            StringBuilder javaScript = new StringBuilder();

            javaScript.append("function soda_bridge(data)");
            javaScript.append("{");
            javaScript.append("return JSON.parse(prompt(data,'__soda__'));");
            javaScript.append("}");

            return javaScript.toString();
        }

        /**
         * Create a JavaScript structure based on Java Object.
         * @param object to be mapped.
         * @param name interface name
         * @return a JavaScript string.
         */
        private String mapObjectToJavaScript(Object object, String name) {
            StringBuilder javaScript = new StringBuilder();
            javaScript.append("function ");
            javaScript.append(name);
            javaScript.append("()");
            javaScript.append("{");
            javaScript.append("};");

            for (Method method : object.getClass().getMethods()) {
                String methodName = method.getName();

                if (Pattern.matches("equals|hashCode|notify|notifyAll|wait", methodName)) {

                    /* Don't be evil. */

                    continue;

                }

                if (methodName.equals("getClass")) {
                    javaScript.append(name);
                    javaScript.append(".");
                    javaScript.append(methodName);
                    javaScript.append(" = function()");
                    javaScript.append("{");
                    javaScript.append("return 'D\\\'oh!';");
                    javaScript.append("};");
                } else if (methodName.equals("toString")) {
                    javaScript.append(name);
                    javaScript.append(".");
                    javaScript.append(methodName);
                    javaScript.append(" = function()");
                    javaScript.append("{");
                    javaScript.append("return 'Soda Interface';");
                    javaScript.append("};");
                } else {
                    int paramCount = 0;
                    javaScript.append(name);
                    javaScript.append(".");
                    javaScript.append(methodName);
                    javaScript.append(" = function");
                    javaScript.append("(");

                    int params = method.getParameterTypes().length;

                    for (int pos = 0; pos < params; pos++) {

                        javaScript.append("param");
                        javaScript.append(paramCount);
                        javaScript.append(",");
                        paramCount++;
                    }

                    if (params > 0)
                        javaScript.deleteCharAt(javaScript.length() - 1);
                    javaScript.append(")");
                    javaScript.append("{");

                    javaScript.append("var data = {'interface':'");
                    ;
                    javaScript.append(name);
                    javaScript.append("','method':'");
                    javaScript.append(methodName);
                    javaScript.append("'");

                    paramCount = 0;
                    javaScript.append(",args:");
                    if (params == 0) {
                        javaScript.append("[]");
                    } else {
                        javaScript.append("[");
                        for (int pos = 0; pos < params; pos++) {
                            if (method.getParameterTypes()[pos] == String.class) {
                                javaScript.append("escape(param");
                                javaScript.append(paramCount);
                                javaScript.append("),");
                            } else {
                                javaScript.append("param");
                                javaScript.append(paramCount);
                                javaScript.append(",");
                            }
                            paramCount++;
                        }
                        javaScript.deleteCharAt(javaScript.length() - 1);
                        javaScript.append("]");
                    }

                    javaScript.append("};");
                    javaScript.append("return soda_bridge(JSON.stringify(data));");
                    javaScript.append("};");
                }
            }

            if (debug)
                Log.i(TAG, javaScript.toString());

            return javaScript.toString();
        }
    }

    class SodaWebChromeClient extends WebChromeClient {
        private WebChromeClient mWebChromeClient;

        @Override
        public void onCloseWindow(WebView window) {
            if (this.mWebChromeClient == null) {
                super.onCloseWindow(window);
            } else {
                this.mWebChromeClient.onCloseWindow(window);
            }
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            boolean ret = false;

            if (this.mWebChromeClient == null) {
                ret = super.onConsoleMessage(consoleMessage);
            } else {
                ret = this.mWebChromeClient.onConsoleMessage(consoleMessage);
            }

            return ret;
        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        @Deprecated
        public void onConsoleMessage(String message, int lineNumber, String sourceID) {

            if (this.mWebChromeClient == null) {
                super.onConsoleMessage(message, lineNumber, sourceID);
            } else {
                this.mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
            }

        }

        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {

            boolean ret = false;

            if (this.mWebChromeClient == null) {
                ret = super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
            } else {
                ret = this.mWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
            }

            return ret;

        }

        @Override
        @Deprecated
        public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
                long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) {

            if (this.mWebChromeClient == null) {
                super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota,
                        quotaUpdater);
            } else {
                this.mWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize,
                        totalQuota, quotaUpdater);
            }
        }

        @Override
        public void onGeolocationPermissionsHidePrompt() {
            // TODO Auto-generated method stub

            if (this.mWebChromeClient == null) {
                super.onGeolocationPermissionsHidePrompt();
            } else {
                this.mWebChromeClient.onGeolocationPermissionsHidePrompt();
            }

        }

        @Override
        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {

            if (this.mWebChromeClient == null) {
                super.onGeolocationPermissionsShowPrompt(origin, callback);
            } else {
                this.mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
            }
        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        public void onHideCustomView() {

            if (this.mWebChromeClient == null) {
                super.onHideCustomView();
            } else {
                this.mWebChromeClient.onHideCustomView();
            }

        }

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

            boolean ret = false;
            if (this.mWebChromeClient == null) {
                ret = super.onJsAlert(view, url, message, result);
            } else {
                ret = this.mWebChromeClient.onJsAlert(view, url, message, result);
            }

            return ret;
        }

        @Override
        public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
            // TODO Auto-generated method stub

            boolean ret = false;
            if (this.mWebChromeClient == null) {
                ret = super.onJsBeforeUnload(view, url, message, result);
            } else {
                ret = mWebChromeClient.onJsBeforeUnload(view, url, message, result);
            }
            return ret;

        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {

            boolean ret = false;
            if (this.mWebChromeClient == null) {
                ret = super.onJsConfirm(view, url, message, result);
            } else {
                ret = this.mWebChromeClient.onJsConfirm(view, url, message, result);
            }
            return ret;
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
                JsPromptResult result) {

            boolean ret = false;
            if (defaultValue != null && defaultValue.equals("__soda__")) {
                HashMap<String, Object> response = new HashMap<String, Object>();

                ret = true;

                try {

                    JSONObject jsonObject = new JSONObject(message);

                    Object object = mSodaWebViewBridge.getInterfaceObject(jsonObject.getString("interface"));

                    JSONArray args = jsonObject.getJSONArray("args");

                    Class[] parameterTypes = new Class[args.length()];
                    Object[] argsList = new Object[args.length()];

                    for (int i = 0; i < args.length(); i++) {
                        Object arg = args.get(i);
                        parameterTypes[i] = arg.getClass();
                        if (parameterTypes[i] == Integer.class)
                            parameterTypes[i] = int.class;
                        argsList[i] = parameterTypes[i] == String.class ? URLDecoder.decode(arg.toString(), "UTF-8")
                                : arg;
                    }

                    Method objectMethod = object.getClass().getMethod(jsonObject.getString("method"),
                            parameterTypes);
                    Object objectResult = objectMethod.invoke(object, argsList);

                    response.put("success", true);
                    response.put("result", objectResult);

                } catch (Exception ex) {
                    response.put("success", false);
                    response.put("result", ex.toString());
                } finally {
                    result.confirm(new JSONObject(response).toString());
                }

            } else if (this.mWebChromeClient == null) {
                ret = super.onJsPrompt(view, url, message, defaultValue, result);
            } else {
                ret = this.mWebChromeClient.onJsPrompt(view, url, message, defaultValue, result);
            }

            return ret;

        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        @Deprecated
        public boolean onJsTimeout() {

            boolean ret = false;

            if (this.mWebChromeClient == null) {
                ret = super.onJsTimeout();
            } else {
                ret = this.mWebChromeClient.onJsTimeout();
            }

            return ret;
        }

        @Override
        public void onProgressChanged(WebView view, int newProgress) {

            if (this.mWebChromeClient == null) {
                super.onProgressChanged(view, newProgress);
            } else {
                this.mWebChromeClient.onProgressChanged(view, newProgress);
            }

        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        @Deprecated
        public void onReachedMaxAppCacheSize(long requiredStorage, long quota, QuotaUpdater quotaUpdater) {

            if (this.mWebChromeClient == null) {
                super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
            } else {
                this.mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
            }

        }

        @Override
        public void onReceivedIcon(WebView view, Bitmap icon) {

            if (this.mWebChromeClient == null) {
                super.onReceivedIcon(view, icon);
            } else {
                this.mWebChromeClient.onReceivedIcon(view, icon);
            }
        }

        @Override
        public void onReceivedTitle(WebView view, String title) {

            if (this.mWebChromeClient == null) {
                super.onReceivedTitle(view, title);
            } else {
                this.mWebChromeClient.onReceivedTitle(view, title);
            }

        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {

            if (this.mWebChromeClient == null) {
                super.onReceivedTouchIconUrl(view, url, precomposed);
            } else {
                this.mWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
            }

        }

        @Override
        public void onRequestFocus(WebView view) {

            if (this.mWebChromeClient == null) {
                super.onRequestFocus(view);
            } else {
                this.mWebChromeClient.onRequestFocus(view);
            }

        }

        @TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {

            if (this.mWebChromeClient == null) {
                super.onShowCustomView(view, callback);
            } else {
                this.mWebChromeClient.onShowCustomView(view, callback);
            }

        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        @Override
        @Deprecated
        public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {

            if (this.mWebChromeClient == null) {
                super.onShowCustomView(view, requestedOrientation, callback);
            } else {
                this.mWebChromeClient.onShowCustomView(view, requestedOrientation, callback);
            }
        }

        public void setWebChromeClient(WebChromeClient client) {
            this.mWebChromeClient = client;
        }
    }

}