From 3692ed09f0a9b1c9a1121e1350b95c5f73578197 Mon Sep 17 00:00:00 2001 From: dreamer Date: Fri, 28 Aug 2015 10:04:59 +0800 Subject: [PATCH] create the project --- package.json | 28 + plugin.xml | 83 ++ src/android/InAppBrowserDialog.java | 49 ++ src/android/InAppChromeClient.java | 132 +++ src/android/InAppCrossBrowser.java | 471 ++++++++++ src/android/WebViewBrowser.java | 831 ++++++++++++++++++ .../res/drawable-hdpi/ic_action_next_item.png | Bin 0 -> 593 bytes .../drawable-hdpi/ic_action_previous_item.png | Bin 0 -> 599 bytes .../res/drawable-hdpi/ic_action_remove.png | Bin 0 -> 438 bytes .../res/drawable-mdpi/ic_action_next_item.png | Bin 0 -> 427 bytes .../drawable-mdpi/ic_action_previous_item.png | Bin 0 -> 438 bytes .../res/drawable-mdpi/ic_action_remove.png | Bin 0 -> 328 bytes .../drawable-xhdpi/ic_action_next_item.png | Bin 0 -> 727 bytes .../ic_action_previous_item.png | Bin 0 -> 744 bytes .../res/drawable-xhdpi/ic_action_remove.png | Bin 0 -> 536 bytes .../drawable-xxhdpi/ic_action_next_item.png | Bin 0 -> 1021 bytes .../ic_action_previous_item.png | Bin 0 -> 1038 bytes .../res/drawable-xxhdpi/ic_action_remove.png | Bin 0 -> 681 bytes www/inappcrossbrowser.js | 105 +++ 19 files changed, 1699 insertions(+) create mode 100644 package.json create mode 100644 plugin.xml create mode 100644 src/android/InAppBrowserDialog.java create mode 100644 src/android/InAppChromeClient.java create mode 100644 src/android/InAppCrossBrowser.java create mode 100644 src/android/WebViewBrowser.java create mode 100644 src/android/res/drawable-hdpi/ic_action_next_item.png create mode 100644 src/android/res/drawable-hdpi/ic_action_previous_item.png create mode 100644 src/android/res/drawable-hdpi/ic_action_remove.png create mode 100644 src/android/res/drawable-mdpi/ic_action_next_item.png create mode 100644 src/android/res/drawable-mdpi/ic_action_previous_item.png create mode 100644 src/android/res/drawable-mdpi/ic_action_remove.png create mode 100644 src/android/res/drawable-xhdpi/ic_action_next_item.png create mode 100644 src/android/res/drawable-xhdpi/ic_action_previous_item.png create mode 100644 src/android/res/drawable-xhdpi/ic_action_remove.png create mode 100644 src/android/res/drawable-xxhdpi/ic_action_next_item.png create mode 100644 src/android/res/drawable-xxhdpi/ic_action_previous_item.png create mode 100644 src/android/res/drawable-xxhdpi/ic_action_remove.png create mode 100644 www/inappcrossbrowser.js diff --git a/package.json b/package.json new file mode 100644 index 0000000..6dc4367 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "version": "0.1.0", + "name": "com.dt-workshop.InAppBrowser", + "cordova_name": "InAppBrowser", + "description": "Cordova android webView and Crosswalk combination", + "license": "Apache 2.0", + "repo": "https://github.com/DT-Workshop/InAppCrossBrowser.git", + "issue": "https://github.com/DT-Workshop/InAppCrossBrowser/issues", + "keywords": [ + "ecosystem:cordova", + "cordova-android", + "cordova", + "in", + "app", + "browser", + "inappbrowser", + "crosswalk" + ], + "platforms": [ + "android" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.1.0" + } + ] +} diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..f24b03e --- /dev/null +++ b/plugin.xml @@ -0,0 +1,83 @@ + + + + + + InAppCrossBrowser + Cordova android webView and Crosswalk combination + Apache 2.0 + cordova,in,app,browser,inappbrowser, crosswalk + https://github.com/DT-Workshop/InAppCrossBrowser.git + https://github.com/DT-Workshop/InAppCrossBrowser/issues + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/InAppBrowserDialog.java b/src/android/InAppBrowserDialog.java new file mode 100644 index 0000000..5cda8b0 --- /dev/null +++ b/src/android/InAppBrowserDialog.java @@ -0,0 +1,49 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 com.dt_workshop.InAppCrossBrowser; + +import android.app.Dialog; +import android.content.Context; + +/** + * Created by Oliver on 22/11/2013. + */ +public class InAppBrowserDialog extends Dialog { + Context context; + WebViewBrowser inAppBrowser = null; + + public InAppBrowserDialog(Context context, int theme) { + super(context, theme); + this.context = context; + } + + public void setInAppBroswer(WebViewBrowser browser) { + this.inAppBrowser = browser; + } + + public void onBackPressed () { + if (this.inAppBrowser == null) { + this.dismiss(); + } else { + // better to go through the in inAppBrowser + // because it does a clean up + this.inAppBrowser.closeDialog(); + } + } +} diff --git a/src/android/InAppChromeClient.java b/src/android/InAppChromeClient.java new file mode 100644 index 0000000..ef245e0 --- /dev/null +++ b/src/android/InAppChromeClient.java @@ -0,0 +1,132 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 com.dt_workshop.InAppCrossBrowser; + +import android.webkit.GeolocationPermissions.Callback; +import android.webkit.JsPromptResult; +import android.webkit.WebChromeClient; +import android.webkit.WebStorage; +import android.webkit.WebView; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.LOG; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; + +public class InAppChromeClient extends WebChromeClient { + + private CordovaWebView webView; + private String LOG_TAG = "InAppChromeClient"; + private long MAX_QUOTA = 100 * 1024 * 1024; + + public InAppChromeClient(CordovaWebView webView) { + super(); + this.webView = webView; + } + /** + * Handle database quota exceeded notification. + * + * @param url + * @param databaseIdentifier + * @param currentQuota + * @param estimatedSize + * @param totalUsedQuota + * @param quotaUpdater + */ + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, + long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) + { + LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota); + quotaUpdater.updateQuota(MAX_QUOTA); + } + + /** + * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. + * + * @param origin + * @param callback + */ + @Override + public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { + super.onGeolocationPermissionsShowPrompt(origin, callback); + callback.invoke(origin, true, false); + } + + /** + * Tell the client to display a prompt dialog to the user. + * If the client returns true, WebView will assume that the client will + * handle the prompt dialog and call the appropriate JsPromptResult method. + * + * The prompt bridge provided for the InAppBrowser is capable of executing any + * oustanding callback belonging to the InAppBrowser plugin. Care has been + * taken that other callbacks cannot be triggered, and that no other code + * execution is possible. + * + * To trigger the bridge, the prompt default value should be of the form: + * + * gap-iab:// + * + * where is the string id of the callback to trigger (something + * like "InAppBrowser0123456789") + * + * If present, the prompt message is expected to be a JSON-encoded value to + * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid. + * + * @param view + * @param url + * @param message + * @param defaultValue + * @param result + */ + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute. + if (defaultValue != null && defaultValue.startsWith("gap")) { + if(defaultValue.startsWith("gap-iab://")) { + PluginResult scriptResult; + String scriptCallbackId = defaultValue.substring(10); + if (scriptCallbackId.startsWith("InAppBrowser")) { + if(message == null || message.length() == 0) { + scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray()); + } else { + try { + scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message)); + } catch(JSONException e) { + scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage()); + } + } + this.webView.sendPluginResult(scriptResult, scriptCallbackId); + result.confirm(""); + return true; + } + } + else + { + // Anything else with a gap: prefix should get this message + LOG.w(LOG_TAG, "InAppBrowser does not support Cordova API calls: " + url + " " + defaultValue); + result.cancel(); + return true; + } + } + return false; + } + +} diff --git a/src/android/InAppCrossBrowser.java b/src/android/InAppCrossBrowser.java new file mode 100644 index 0000000..988ebbc --- /dev/null +++ b/src/android/InAppCrossBrowser.java @@ -0,0 +1,471 @@ +package com.dt_workshop.InAppCrossBrowser; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.InputType; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.webkit.CookieManager; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import org.apache.cordova.CordovaWebView; +import org.chromium.net.NetError; +import org.crosswalk.engine.XWalkCordovaResourceClient; +import org.crosswalk.engine.XWalkCordovaUiClient; +import org.crosswalk.engine.XWalkWebViewEngine; +import org.json.JSONException; +import org.json.JSONObject; +import org.xwalk.core.XWalkResourceClient; +import org.xwalk.core.XWalkView; + +import java.util.HashMap; + +public class InAppCrossBrowser extends WebViewBrowser { + + private XWalkView xWalkView = null; + + /** + * Closes the dialog + */ + public void closeDialog() { + final WebView childView = this.inAppWebView; + // The JS protects against multiple calls, so this should happen only when + // closeDialog() is called by other native code. + if (childView == null) { + return; + } + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + childView.setWebViewClient(new WebViewClient() { + // NB: wait for about:blank before dismissing + public void onPageFinished(WebView view, String url) { + if (dialog != null) { + { + dialog.dismiss(); + dialog = null; + + xWalkView.onDestroy(); + xWalkView = null; + + cordova.getActivity() + .getWindow() + .getDecorView() + .findViewById(android.R.id.content) + .postInvalidateDelayed(300); + } + } + } + }); + + // NB: From SDK 19: "If you call methods on WebView from any thread + // other than your app's UI thread, it can cause unexpected results." + // http://developer.android.com/guide/webapps/migrating.html#Threads + childView.loadUrl("about:blank"); + inAppWebView = null; + } + }); + + try { + JSONObject obj = new JSONObject(); + obj.put("type", EXIT_EVENT); + sendUpdate(obj, false); + } catch (JSONException ex) { + Log.d(LOG_TAG, "Should never happen"); + } + } + + protected void loadURL(String url) { + if (xWalkView != null) { + xWalkView.load(url, null); + } else { + inAppWebView.loadUrl(url); + } + } + + protected WebView createBuildInWebView() { + final CordovaWebView thatWebView = this.webView; + // Edit Text Box + edittext = new EditText(cordova.getActivity()); + RelativeLayout.LayoutParams textLayoutParams = + new RelativeLayout.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); + textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); + edittext.setLayoutParams(textLayoutParams); + edittext.setId(4); + edittext.setSingleLine(true); + edittext.setText(""); + edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); + edittext.setImeOptions(EditorInfo.IME_ACTION_GO); + edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE + edittext.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is a key-down event on the "enter" button + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // navigate(edittext.getText().toString()); + return true; + } + return false; + } + }); + + inAppWebView = new WebView(cordova.getActivity()); + inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT)); + inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); + WebViewClient client = new InAppBrowserClient(thatWebView, edittext); + inAppWebView.setWebViewClient(client); + WebSettings settings = inAppWebView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + settings.setBuiltInZoomControls(true); + settings.setPluginState(android.webkit.WebSettings.PluginState.ON); + + //Toggle whether this is enabled or not! + Bundle appSettings = cordova.getActivity().getIntent().getExtras(); + boolean enableDatabase = + appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); + if (enableDatabase) { + String databasePath = cordova.getActivity() + .getApplicationContext() + .getDir("inAppBrowserDB", Context.MODE_PRIVATE) + .getPath(); + settings.setDatabasePath(databasePath); + settings.setDatabaseEnabled(true); + } + settings.setDomStorageEnabled(true); + + if (clearAllCache) { + CookieManager.getInstance().removeAllCookie(); + } else if (clearSessionCache) { + CookieManager.getInstance().removeSessionCookie(); + } + + inAppWebView.setId(6); + inAppWebView.getSettings().setLoadWithOverviewMode(true); + inAppWebView.getSettings().setUseWideViewPort(true); + inAppWebView.requestFocus(); + inAppWebView.requestFocusFromTouch(); + + return inAppWebView; + } + + /** + * copy from XWalkCordovaResourceClient.convertErrorCode + */ + public static int convertErrorCode(int netError) { + // Note: many NetError.Error constants don't have an obvious mapping. + // These will be handled by the default case, ERROR_UNKNOWN. + switch (netError) { + case NetError.ERR_UNSUPPORTED_AUTH_SCHEME: + return XWalkCordovaResourceClient.ERROR_UNSUPPORTED_AUTH_SCHEME; + + case NetError.ERR_INVALID_AUTH_CREDENTIALS: + case NetError.ERR_MISSING_AUTH_CREDENTIALS: + case NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT: + return XWalkCordovaResourceClient.ERROR_AUTHENTICATION; + + case NetError.ERR_TOO_MANY_REDIRECTS: + return XWalkCordovaResourceClient.ERROR_REDIRECT_LOOP; + + case NetError.ERR_UPLOAD_FILE_CHANGED: + return XWalkCordovaResourceClient.ERROR_FILE_NOT_FOUND; + + case NetError.ERR_INVALID_URL: + return XWalkCordovaResourceClient.ERROR_BAD_URL; + + case NetError.ERR_DISALLOWED_URL_SCHEME: + case NetError.ERR_UNKNOWN_URL_SCHEME: + return XWalkCordovaResourceClient.ERROR_UNSUPPORTED_SCHEME; + + case NetError.ERR_IO_PENDING: + case NetError.ERR_NETWORK_IO_SUSPENDED: + return XWalkCordovaResourceClient.ERROR_IO; + + case NetError.ERR_CONNECTION_TIMED_OUT: + case NetError.ERR_TIMED_OUT: + return XWalkCordovaResourceClient.ERROR_TIMEOUT; + + case NetError.ERR_FILE_TOO_BIG: + return XWalkCordovaResourceClient.ERROR_FILE; + + case NetError.ERR_HOST_RESOLVER_QUEUE_TOO_LARGE: + case NetError.ERR_INSUFFICIENT_RESOURCES: + case NetError.ERR_OUT_OF_MEMORY: + return XWalkCordovaResourceClient.ERROR_TOO_MANY_REQUESTS; + + case NetError.ERR_CONNECTION_CLOSED: + case NetError.ERR_CONNECTION_RESET: + case NetError.ERR_CONNECTION_REFUSED: + case NetError.ERR_CONNECTION_ABORTED: + case NetError.ERR_CONNECTION_FAILED: + case NetError.ERR_SOCKET_NOT_CONNECTED: + return XWalkCordovaResourceClient.ERROR_CONNECT; + + case NetError.ERR_INTERNET_DISCONNECTED: + case NetError.ERR_ADDRESS_INVALID: + case NetError.ERR_ADDRESS_UNREACHABLE: + case NetError.ERR_NAME_NOT_RESOLVED: + case NetError.ERR_NAME_RESOLUTION_FAILED: + return XWalkCordovaResourceClient.ERROR_HOST_LOOKUP; + + case NetError.ERR_SSL_PROTOCOL_ERROR: + case NetError.ERR_SSL_CLIENT_AUTH_CERT_NEEDED: + case NetError.ERR_TUNNEL_CONNECTION_FAILED: + case NetError.ERR_NO_SSL_VERSIONS_ENABLED: + case NetError.ERR_SSL_VERSION_OR_CIPHER_MISMATCH: + case NetError.ERR_SSL_RENEGOTIATION_REQUESTED: + case NetError.ERR_CERT_ERROR_IN_SSL_RENEGOTIATION: + case NetError.ERR_BAD_SSL_CLIENT_AUTH_CERT: + case NetError.ERR_SSL_NO_RENEGOTIATION: + case NetError.ERR_SSL_DECOMPRESSION_FAILURE_ALERT: + case NetError.ERR_SSL_BAD_RECORD_MAC_ALERT: + case NetError.ERR_SSL_UNSAFE_NEGOTIATION: + case NetError.ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: + case NetError.ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED: + case NetError.ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY: + return XWalkCordovaResourceClient.ERROR_FAILED_SSL_HANDSHAKE; + + case NetError.ERR_PROXY_AUTH_UNSUPPORTED: + case NetError.ERR_PROXY_AUTH_REQUESTED: + case NetError.ERR_PROXY_CONNECTION_FAILED: + case NetError.ERR_UNEXPECTED_PROXY_AUTH: + return XWalkCordovaResourceClient.ERROR_PROXY_AUTHENTICATION; + + // The certificate errors are handled by onReceivedSslError + // and don't need to be reported here. + case NetError.ERR_CERT_COMMON_NAME_INVALID: + case NetError.ERR_CERT_DATE_INVALID: + case NetError.ERR_CERT_AUTHORITY_INVALID: + case NetError.ERR_CERT_CONTAINS_ERRORS: + case NetError.ERR_CERT_NO_REVOCATION_MECHANISM: + case NetError.ERR_CERT_UNABLE_TO_CHECK_REVOCATION: + case NetError.ERR_CERT_REVOKED: + case NetError.ERR_CERT_INVALID: + case NetError.ERR_CERT_WEAK_SIGNATURE_ALGORITHM: + case NetError.ERR_CERT_NON_UNIQUE_NAME: + return XWalkCordovaResourceClient.ERROR_OK; + + default: + return XWalkCordovaResourceClient.ERROR_UNKNOWN; + } + } + + protected void attachWebView(ViewGroup viewGroup) { + View view = createBuildInWebView(); + + if (webView.getEngine() instanceof XWalkWebViewEngine) { + xWalkView = new XWalkView(cordova.getActivity(), (AttributeSet) null); + + xWalkView.setUIClient(new XWalkCordovaUiClient((XWalkWebViewEngine) webView.getEngine()) { + InAppBrowserClient inAppBrowserClient = new InAppBrowserClient(webView, edittext); + + @Override + public void onPageLoadStarted(XWalkView view, String url) { + inAppBrowserClient.onPageStarted(inAppWebView, url, null); + } + + @Override + public void onPageLoadStopped(XWalkView view, String url, LoadStatus status) { + inAppBrowserClient.onPageFinished(inAppWebView, view.getUrl()); + } + }); + + xWalkView.setResourceClient(new XWalkResourceClient(xWalkView) { + InAppBrowserClient inAppBrowserClient = new InAppBrowserClient(webView, edittext); + + @Override + public void onReceivedLoadError(XWalkView view, + int errorCode, + String description, + String failingUrl) { + inAppBrowserClient.onReceivedError(inAppWebView, + convertErrorCode(errorCode), + description, + failingUrl); + } + }); + + view = xWalkView; + } + + viewGroup.addView(view); + } + + /** + * Convert our DIP units to Pixels + * + * @return int + */ + private int dpToPixels(int dipValue) { + int value = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + (float) dipValue, + cordova.getActivity().getResources().getDisplayMetrics()); + + return value; + } + + protected InAppBrowserDialog createBrowserDialog() { + // Let's create the main dialog + InAppBrowserDialog dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); + dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setCancelable(true); + dialog.setInAppBroswer(getInAppBrowser()); + + // Main container layout + final LinearLayout main = new LinearLayout(cordova.getActivity()); + main.setOrientation(LinearLayout.VERTICAL); + + // Toolbar layout + RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); + //Please, no more black! + toolbar.setBackgroundColor(android.graphics.Color.LTGRAY); + toolbar.setLayoutParams(new RelativeLayout.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, + this.dpToPixels(44))); + toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2)); + toolbar.setHorizontalGravity(Gravity.LEFT); + toolbar.setVerticalGravity(Gravity.TOP); + + // Action Button Container layout + RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity()); + actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT)); + actionButtonContainer.setHorizontalGravity(Gravity.LEFT); + actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); + actionButtonContainer.setId(1); + + Resources activityRes = cordova.getActivity().getResources(); + + // Back button + // Button back = new Button(cordova.getActivity()); + // RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.MATCH_PARENT); + // backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT); + // back.setLayoutParams(backLayoutParams); + // back.setContentDescription("Back Button"); + // back.setId(2); + // int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName()); + // Drawable backIcon = activityRes.getDrawable(backResId); + // if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + // back.setBackgroundDrawable(backIcon); + // } else { + // back.setBackground(backIcon); + // } + // + // back.setOnClickListener(new View.OnClickListener() { + // public void onClick(View v) { + // goBack(); + // } + // }); + + // Close/Done button + Button close = new Button(cordova.getActivity()); + RelativeLayout.LayoutParams closeLayoutParams = + new RelativeLayout.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.MATCH_PARENT); + closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + close.setLayoutParams(closeLayoutParams); + close.setContentDescription("Close Button"); + close.setId(5); + int closeResId = + activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName()); + Drawable closeIcon = activityRes.getDrawable(closeResId); + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + close.setBackgroundDrawable(closeIcon); + } else { + close.setBackground(closeIcon); + } + close.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + closeDialog(); + } + }); + + // Add the back and forward buttons to our action button container layout + // actionButtonContainer.addView(back); + toolbar.addView(actionButtonContainer); + toolbar.addView(close); + + // Add our toolbar to our main view/layout + main.addView(toolbar); + + // Add main webview to our main view/layout + attachWebView(main); + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.copyFrom(dialog.getWindow().getAttributes()); + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.MATCH_PARENT; + + dialog.setContentView(main); + dialog.getWindow().setAttributes(lp); + + return dialog; + } + + @Override + public String showWebPage(final String url, HashMap features) { + + // Determine if we should hide the location bar. + showLocationBar = true; + openWindowHidden = false; + if (features != null) { + Boolean show = features.get(LOCATION); + if (show != null) { + showLocationBar = show.booleanValue(); + } + Boolean hidden = features.get(HIDDEN); + if (hidden != null) { + openWindowHidden = hidden.booleanValue(); + } + Boolean cache = features.get(CLEAR_ALL_CACHE); + if (cache != null) { + clearAllCache = cache.booleanValue(); + } else { + cache = features.get(CLEAR_SESSION_CACHE); + if (cache != null) { + clearSessionCache = cache.booleanValue(); + } + } + } + + dialog = createBrowserDialog(); + + // Create dialog in new thread + Runnable runnable = new Runnable() { + // @SuppressLint("NewApi") + public void run() { + + loadURL(url); + + if (openWindowHidden) { + dialog.hide(); + } else { + dialog.show(); + } + + } + }; + + this.cordova.getActivity().runOnUiThread(runnable); + + return ""; + } + +} diff --git a/src/android/WebViewBrowser.java b/src/android/WebViewBrowser.java new file mode 100644 index 0000000..c9e0d84 --- /dev/null +++ b/src/android/WebViewBrowser.java @@ -0,0 +1,831 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 com.dt_workshop.InAppCrossBrowser; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.InputType; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.webkit.CookieManager; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.Config; +import org.apache.cordova.CordovaArgs; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.LOG; +import org.apache.cordova.PluginManager; +import org.apache.cordova.PluginResult; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.StringTokenizer; + +@SuppressLint("SetJavaScriptEnabled") +public class WebViewBrowser extends CordovaPlugin { + + protected static final String NULL = "null"; + protected static final String LOG_TAG = "InAppBrowser"; + protected static final String SELF = "_self"; + protected static final String SYSTEM = "_system"; + // protected static final String BLANK = "_blank"; + protected static final String EXIT_EVENT = "exit"; + protected static final String LOCATION = "location"; + protected static final String HIDDEN = "hidden"; + protected static final String LOAD_START_EVENT = "loadstart"; + protected static final String LOAD_STOP_EVENT = "loadstop"; + protected static final String LOAD_ERROR_EVENT = "loaderror"; + protected static final String CLEAR_ALL_CACHE = "clearcache"; + protected static final String CLEAR_SESSION_CACHE = "clearsessioncache"; + + protected InAppBrowserDialog dialog; + protected WebView inAppWebView; + protected EditText edittext; + protected CallbackContext callbackContext; + protected boolean showLocationBar = true; + protected boolean openWindowHidden = false; + protected boolean clearAllCache= false; + protected boolean clearSessionCache=false; + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @return A PluginResult object with a status and message. + */ + public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { + if (action.equals("open")) { + this.callbackContext = callbackContext; + final String url = args.getString(0); + String t = args.optString(1); + if (t == null || t.equals("") || t.equals(NULL)) { + t = SELF; + } + final String target = t; + final HashMap features = parseFeature(args.optString(2)); + + Log.d(LOG_TAG, "target = " + target); + + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + String result = ""; + // SELF + if (SELF.equals(target)) { + Log.d(LOG_TAG, "in self"); + /* This code exists for compatibility between 3.x and 4.x versions of Cordova. + * Previously the Config class had a static method, isUrlWhitelisted(). That + * responsibility has been moved to the plugins, with an aggregating method in + * PluginManager. + */ + Boolean shouldAllowNavigation = null; + if (url.startsWith("javascript:")) { + shouldAllowNavigation = true; + } + if (shouldAllowNavigation == null) { + try { + Method iuw = Config.class.getMethod("isUrlWhiteListed", String.class); + shouldAllowNavigation = (Boolean)iuw.invoke(null, url); + } catch (NoSuchMethodException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + } + if (shouldAllowNavigation == null) { + try { + Method gpm = webView.getClass().getMethod("getPluginManager"); + PluginManager pm = (PluginManager)gpm.invoke(webView); + Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class); + shouldAllowNavigation = (Boolean)san.invoke(pm, url); + } catch (NoSuchMethodException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + } + // load in webview + if (Boolean.TRUE.equals(shouldAllowNavigation)) { + Log.d(LOG_TAG, "loading in webview"); + webView.loadUrl(url); + } + //Load the dialer + else if (url.startsWith(WebView.SCHEME_TEL)) + { + try { + Log.d(LOG_TAG, "loading in dialer"); + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); + } + } + // load in InAppBrowser + else { + Log.d(LOG_TAG, "loading in InAppBrowser"); + result = showWebPage(url, features); + } + } + // SYSTEM + else if (SYSTEM.equals(target)) { + Log.d(LOG_TAG, "in system"); + result = openExternal(url); + } + // BLANK - or anything else + else { + Log.d(LOG_TAG, "in blank"); + result = showWebPage(url, features); + } + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + } + }); + } + else if (action.equals("close")) { + closeDialog(); + } + else if (action.equals("injectScriptCode")) { + String jsWrapper = null; + if (args.getBoolean(1)) { + jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId()); + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectScriptFile")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectStyleCode")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("injectStyleFile")) { + String jsWrapper; + if (args.getBoolean(1)) { + jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId()); + } else { + jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)"; + } + injectDeferredObject(args.getString(0), jsWrapper); + } + else if (action.equals("show")) { + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + dialog.show(); + } + }); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); + pluginResult.setKeepCallback(true); + this.callbackContext.sendPluginResult(pluginResult); + } + else { + return false; + } + return true; + } + + /** + * Called when the view navigates. + */ + @Override + public void onReset() { + closeDialog(); + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + closeDialog(); + } + + /** + * Inject an object (script or style) into the InAppBrowser WebView. + * + * This is a helper method for the inject{Script|Style}{Code|File} API calls, which + * provides a consistent method for injecting JavaScript code into the document. + * + * If a wrapper string is supplied, then the source string will be JSON-encoded (adding + * quotes) and wrapped using string formatting. (The wrapper string should have a single + * '%s' marker) + * + * @param source The source object (filename or script/style text) to inject into + * the document. + * @param jsWrapper A JavaScript string to wrap the source string in, so that the object + * is properly injected, or null if the source string is JavaScript text + * which should be executed directly. + */ + protected void injectDeferredObject(String source, String jsWrapper) { + String scriptToInject; + if (jsWrapper != null) { + org.json.JSONArray jsonEsc = new org.json.JSONArray(); + jsonEsc.put(source); + String jsonRepr = jsonEsc.toString(); + String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); + scriptToInject = String.format(jsWrapper, jsonSourceString); + } else { + scriptToInject = source; + } + final String finalScriptToInject = scriptToInject; + this.cordova.getActivity().runOnUiThread(new Runnable() { + @SuppressLint("NewApi") + @Override + public void run() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + // This action will have the side-effect of blurring the currently focused element + inAppWebView.loadUrl("javascript:" + finalScriptToInject); + } else { + inAppWebView.evaluateJavascript(finalScriptToInject, null); + } + } + }); + } + + /** + * Put the list of features into a hash map + * + * @param optString + * @return + */ + protected HashMap parseFeature(String optString) { + if (optString.equals(NULL)) { + return null; + } else { + HashMap map = new HashMap(); + StringTokenizer features = new StringTokenizer(optString, ","); + StringTokenizer option; + while(features.hasMoreElements()) { + option = new StringTokenizer(features.nextToken(), "="); + if (option.hasMoreElements()) { + String key = option.nextToken(); + Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE; + map.put(key, value); + } + } + return map; + } + } + + /** + * Display a new browser with the specified URL. + * + * @param url The url to load. + * @return "" if ok, or error message. + */ + public String openExternal(String url) { + try { + Intent intent = null; + intent = new Intent(Intent.ACTION_VIEW); + // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". + // Adding the MIME type to http: URLs causes them to not be handled by the downloader. + Uri uri = Uri.parse(url); + if ("file".equals(uri.getScheme())) { + intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri)); + } else { + intent.setData(uri); + } + this.cordova.getActivity().startActivity(intent); + return ""; + } catch (android.content.ActivityNotFoundException e) { + Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString()); + return e.toString(); + } + } + + /** + * Closes the dialog + */ + public void closeDialog() { + final WebView childView = this.inAppWebView; + // The JS protects against multiple calls, so this should happen only when + // closeDialog() is called by other native code. + if (childView == null) { + return; + } + this.cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + childView.setWebViewClient(new WebViewClient() { + // NB: wait for about:blank before dismissing + public void onPageFinished(WebView view, String url) { + if (dialog != null) { + dialog.dismiss(); + } + } + }); + // NB: From SDK 19: "If you call methods on WebView from any thread + // other than your app's UI thread, it can cause unexpected results." + // http://developer.android.com/guide/webapps/migrating.html#Threads + childView.loadUrl("about:blank"); + } + }); + + try { + JSONObject obj = new JSONObject(); + obj.put("type", EXIT_EVENT); + sendUpdate(obj, false); + } catch (JSONException ex) { + Log.d(LOG_TAG, "Should never happen"); + } + } + + /** + * Checks to see if it is possible to go back one page in history, then does so. + */ + protected void goBack() { + if (this.inAppWebView.canGoBack()) { + this.inAppWebView.goBack(); + } + } + + /** + * Checks to see if it is possible to go forward one page in history, then does so. + */ + protected void goForward() { + if (this.inAppWebView.canGoForward()) { + this.inAppWebView.goForward(); + } + } + + /** + * Navigate to the new page + * + * @param url to load + */ + protected void navigate(String url) { + InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); + + if (!url.startsWith("http") && !url.startsWith("file:")) { + this.inAppWebView.loadUrl("http://" + url); + } else { + this.inAppWebView.loadUrl(url); + } + this.inAppWebView.requestFocus(); + } + + + /** + * Should we show the location bar? + * + * @return boolean + */ + protected boolean getShowLocationBar() { + return this.showLocationBar; + } + + protected WebViewBrowser getInAppBrowser(){ + return this; + } + + /** + * Display a new browser with the specified URL. + * + * @param url The url to load. + */ + public String showWebPage(final String url, HashMap features) { + // Determine if we should hide the location bar. + showLocationBar = true; + openWindowHidden = false; + if (features != null) { + Boolean show = features.get(LOCATION); + if (show != null) { + showLocationBar = show.booleanValue(); + } + Boolean hidden = features.get(HIDDEN); + if (hidden != null) { + openWindowHidden = hidden.booleanValue(); + } + Boolean cache = features.get(CLEAR_ALL_CACHE); + if (cache != null) { + clearAllCache = cache.booleanValue(); + } else { + cache = features.get(CLEAR_SESSION_CACHE); + if (cache != null) { + clearSessionCache = cache.booleanValue(); + } + } + } + + final CordovaWebView thatWebView = this.webView; + + // Create dialog in new thread + Runnable runnable = new Runnable() { + /** + * Convert our DIP units to Pixels + * + * @return int + */ + private int dpToPixels(int dipValue) { + int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, + (float) dipValue, + cordova.getActivity().getResources().getDisplayMetrics() + ); + + return value; + } + + @SuppressLint("NewApi") + public void run() { + // Let's create the main dialog + dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); + dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + dialog.setCancelable(true); + dialog.setInAppBroswer(getInAppBrowser()); + + // Main container layout + LinearLayout main = new LinearLayout(cordova.getActivity()); + main.setOrientation(LinearLayout.VERTICAL); + + // Toolbar layout + RelativeLayout toolbar = new RelativeLayout(cordova.getActivity()); + //Please, no more black! + toolbar.setBackgroundColor(android.graphics.Color.LTGRAY); + toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44))); + toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2)); + toolbar.setHorizontalGravity(Gravity.LEFT); + toolbar.setVerticalGravity(Gravity.TOP); + + // Action Button Container layout + RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity()); + actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + actionButtonContainer.setHorizontalGravity(Gravity.LEFT); + actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL); + actionButtonContainer.setId(1); + + // Back button + Button back = new Button(cordova.getActivity()); + RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); + backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT); + back.setLayoutParams(backLayoutParams); + back.setContentDescription("Back Button"); + back.setId(2); + Resources activityRes = cordova.getActivity().getResources(); + int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName()); + Drawable backIcon = activityRes.getDrawable(backResId); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + { + back.setBackgroundDrawable(backIcon); + } + else + { + back.setBackground(backIcon); + } + back.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + goBack(); + } + }); + + // Forward button + Button forward = new Button(cordova.getActivity()); + RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); + forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2); + forward.setLayoutParams(forwardLayoutParams); + forward.setContentDescription("Forward Button"); + forward.setId(3); + int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName()); + Drawable fwdIcon = activityRes.getDrawable(fwdResId); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + { + forward.setBackgroundDrawable(fwdIcon); + } + else + { + forward.setBackground(fwdIcon); + } + forward.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + goForward(); + } + }); + + // Edit Text Box + edittext = new EditText(cordova.getActivity()); + RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1); + textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5); + edittext.setLayoutParams(textLayoutParams); + edittext.setId(4); + edittext.setSingleLine(true); + edittext.setText(url); + edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI); + edittext.setImeOptions(EditorInfo.IME_ACTION_GO); + edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE + edittext.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is a key-down event on the "enter" button + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + navigate(edittext.getText().toString()); + return true; + } + return false; + } + }); + + // Close/Done button + Button close = new Button(cordova.getActivity()); + RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); + closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + close.setLayoutParams(closeLayoutParams); + forward.setContentDescription("Close Button"); + close.setId(5); + int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName()); + Drawable closeIcon = activityRes.getDrawable(closeResId); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + { + close.setBackgroundDrawable(closeIcon); + } + else + { + close.setBackground(closeIcon); + } + close.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + closeDialog(); + } + }); + + // WebView + inAppWebView = new WebView(cordova.getActivity()); + inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView)); + WebViewClient client = new InAppBrowserClient(thatWebView, edittext); + inAppWebView.setWebViewClient(client); + WebSettings settings = inAppWebView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + settings.setBuiltInZoomControls(true); + settings.setPluginState(WebSettings.PluginState.ON); + + //Toggle whether this is enabled or not! + Bundle appSettings = cordova.getActivity().getIntent().getExtras(); + boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); + if (enableDatabase) { + String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath(); + settings.setDatabasePath(databasePath); + settings.setDatabaseEnabled(true); + } + settings.setDomStorageEnabled(true); + + if (clearAllCache) { + CookieManager.getInstance().removeAllCookie(); + } else if (clearSessionCache) { + CookieManager.getInstance().removeSessionCookie(); + } + + inAppWebView.loadUrl(url); + inAppWebView.setId(6); + inAppWebView.getSettings().setLoadWithOverviewMode(true); + inAppWebView.getSettings().setUseWideViewPort(true); + inAppWebView.requestFocus(); + inAppWebView.requestFocusFromTouch(); + + // Add the back and forward buttons to our action button container layout + actionButtonContainer.addView(back); + actionButtonContainer.addView(forward); + + // Add the views to our toolbar + toolbar.addView(actionButtonContainer); + toolbar.addView(edittext); + toolbar.addView(close); + + // Don't add the toolbar if its been disabled + if (getShowLocationBar()) { + // Add our toolbar to our main view/layout + main.addView(toolbar); + } + + // Add our webview to our main view/layout + main.addView(inAppWebView); + + LayoutParams lp = new LayoutParams(); + lp.copyFrom(dialog.getWindow().getAttributes()); + lp.width = LayoutParams.MATCH_PARENT; + lp.height = LayoutParams.MATCH_PARENT; + + dialog.setContentView(main); + dialog.show(); + dialog.getWindow().setAttributes(lp); + // the goal of openhidden is to load the url and not display it + // Show() needs to be called to cause the URL to be loaded + if(openWindowHidden) { + dialog.hide(); + } + } + }; + this.cordova.getActivity().runOnUiThread(runnable); + return ""; + } + + /** + * Create a new plugin success result and send it back to JavaScript + * + * @param obj a JSONObject contain event payload information + */ + protected void sendUpdate(JSONObject obj, boolean keepCallback) { + sendUpdate(obj, keepCallback, PluginResult.Status.OK); + } + + /** + * Create a new plugin result and send it back to JavaScript + * + * @param obj a JSONObject contain event payload information + * @param status the status code to return to the JavaScript environment + */ + protected void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) { + if (callbackContext != null) { + PluginResult result = new PluginResult(status, obj); + result.setKeepCallback(keepCallback); + callbackContext.sendPluginResult(result); + if (!keepCallback) { + callbackContext = null; + } + } + } + + /** + * The webview client receives notifications about appView + */ + public class InAppBrowserClient extends WebViewClient { + EditText edittext; + CordovaWebView webView; + + /** + * Constructor. + */ + public InAppBrowserClient(CordovaWebView webView, EditText mEditText) { + this.webView = webView; + this.edittext = mEditText; + } + + /** + * Notify the host application that a page has started loading. + * + * @param view The webview initiating the callback. + * @param url The url of the page. + */ + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + String newloc = ""; + if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) { + newloc = url; + } + // If dialing phone (tel:5551212) + else if (url.startsWith(WebView.SCHEME_TEL)) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); + } + } + + else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString()); + } + } + // If sms:5551212?body=This is the message + else if (url.startsWith("sms:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + + // Get address + String address = null; + int parmIndex = url.indexOf('?'); + if (parmIndex == -1) { + address = url.substring(4); + } + else { + address = url.substring(4, parmIndex); + + // If body, then set sms body + Uri uri = Uri.parse(url); + String query = uri.getQuery(); + if (query != null) { + if (query.startsWith("body=")) { + intent.putExtra("sms_body", query.substring(5)); + } + } + } + intent.setData(Uri.parse("sms:" + address)); + intent.putExtra("address", address); + intent.setType("vnd.android-dir/mms-sms"); + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); + } + } + else { + newloc = "http://" + url; + } + + if (!newloc.equals(edittext.getText().toString())) { + edittext.setText(newloc); + } + + try { + JSONObject obj = new JSONObject(); + obj.put("type", LOAD_START_EVENT); + obj.put("url", newloc); + + sendUpdate(obj, true); + } catch (JSONException ex) { + Log.d(LOG_TAG, "Should never happen"); + } + } + + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + + try { + JSONObject obj = new JSONObject(); + obj.put("type", LOAD_STOP_EVENT); + obj.put("url", url); + + sendUpdate(obj, true); + } catch (JSONException ex) { + Log.d(LOG_TAG, "Should never happen"); + } + } + + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + + try { + JSONObject obj = new JSONObject(); + obj.put("type", LOAD_ERROR_EVENT); + obj.put("url", failingUrl); + obj.put("code", errorCode); + obj.put("message", description); + + sendUpdate(obj, true, PluginResult.Status.ERROR); + } catch (JSONException ex) { + Log.d(LOG_TAG, "Should never happen"); + } + } + } +} diff --git a/src/android/res/drawable-hdpi/ic_action_next_item.png b/src/android/res/drawable-hdpi/ic_action_next_item.png new file mode 100644 index 0000000000000000000000000000000000000000..fa469d8896a59934fbf36aebe69bbb8caec2a8b2 GIT binary patch literal 593 zcmV-X0@2ew#Qp~KZ0)f7AEsw$G6WMfh>{dU&BP{V_`c+kTrSs|-ZgLGzR9J{ zy{!4Zy}Qr-2m&uJFE6kEtgfvOjmP6j9LKw&D0Xco(6L}V!s7|;5^az6lBQ{S5*2{? zJ|53#L+(1GL{N!0RN~l10%617)4$)>`UYGP`4yn?j(__Huh&|rkN$JSH{vHTOn$>G zjroyY3G^Qj{}fLlnEZm-NSOyw0vcYoc)is^J==Kgx}pOoQH9rt zCcWqc6ufqLpQLJN3$IQS;3Tpdyk>}(q7&dGHZV&=sb@%H+hmOhgA-RP31AVM8io)l z0d{lI3BddV9yf;20_HQ(6@Ym%es2h^V16xn2=U%$Y6z`iekpnmuzAzAA&2>e=t-;` z@*~|m+FUe+*!;|JPNt3BD{u5fV{}r1%Xw2=I!}NuwrX&hyMje%$vhdqGo8?E;nG8b)mmC}IAq^-?d)0?>iAL56M&OH;S)f;MS6moTS;^pP# f<>ghSUjYUH07Aer2Yy7P00000NkvXXu0mjfn)D0> literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-hdpi/ic_action_previous_item.png b/src/android/res/drawable-hdpi/ic_action_previous_item.png new file mode 100644 index 0000000000000000000000000000000000000000..e861ecce9272c9c8192ff3935dc3445b3f8edd85 GIT binary patch literal 599 zcmV-d0;v6oP)t91EdOD4Sj+@^zv0XTmRam(r!l+aCYZn=AYlfBU}9$P%RX`G3S#ZobXi~09O)yy z_h@xNFv?|&XZIb8t!Dh@ekK#%6En0)S$V{i% zY%aFj?ZY9#DlC*E@g#g;pq^aCrI-PTZ=ZYgT*a&S z#TG=aPQ>d5{Cm&_O7VJ;d|zah(XowJpT*q75U)EvZiYPrrFdQNsg2^5YNUH4);{@5 laDBZV5fKp)@xSpUzyOzByV literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-hdpi/ic_action_remove.png b/src/android/res/drawable-hdpi/ic_action_remove.png new file mode 100644 index 0000000000000000000000000000000000000000..f889617e4471d6a0c65dd81a060bea482276b322 GIT binary patch literal 438 zcmV;n0ZIOeP)uR$+`ZL}CL9iu4pv&~TPqL?R?{{9XWYPqIRgeD^;8#3vi0QmIrbmFkC- z=lNP%LWrBV*rEMwV3MsoHqsVpnjWjFdZ5h9V<&ATapw~E1PAb=v+=Jp6hzH@=Ksnh z?p@-Z`}Cg%jQ{`u07*qoM6N<$g0AVdX8-^I literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-mdpi/ic_action_next_item.png b/src/android/res/drawable-mdpi/ic_action_next_item.png new file mode 100644 index 0000000000000000000000000000000000000000..47365a300110db23c24d8471b4412cff3299335e GIT binary patch literal 427 zcmV;c0aX5pP)}4 zNQyzD&z|KhmJh*V!SWC28k$rgFbJCTOw9>SV0e#=m{d{d4v_RE6S|M4@9pf)?AB@( zD!dzlAQ+YTy?O!R7Jg<39}z}Tv@F9wqrkgk5hq4vW#Z%37t=J|d!9GIV_T>J#tHae zs|*lx#8HhW$b@f_0m;G{vp(Q)h-gX-!EN#1zjFg(;tztmoQbZOIJ;hgD^LMkhP9%& z3{#T~s7xf7HPhY~s)O+-_}IjNm^e^Wm8VZr!UUxt|dS5ABh@z}BtY}8#KEK=()eJQO_;pw;tFaVN+ VbL}20dqe;L002ovPDHLkV1lYyvRnWF literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-mdpi/ic_action_previous_item.png b/src/android/res/drawable-mdpi/ic_action_previous_item.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad2df42755874f331733cfab5ad0931d5baf0a1 GIT binary patch literal 438 zcmV;n0ZIOeP)F6XU^oF|V`EEF^)OP= z@&q6Sa=>FC?mK(->^EZcF;UUyFyG4pu`{uHnW$j70gy^S5_$~u{bss20BAYLxADl9 zgMyzFGngo_To_0d0WlX;2o&cFiH&=T900T&WN9Xv<)DQ3m{haK4S`H(VuOKIN}9B4HXgw@Cj>q2Ew4lGVMbE8VbLGzP<|0lB`f{0u;Ljl>0<;2S6S19b_TMVkAc>0>#dN z97vaJ2rYgWA_>VtD=6BB08&W203|{o(*P>O59Bif#c$EAAcEEf$C26udk3UcBd1Y1 g3P!PbXFRCwBAU>F6XU=$26V8kbBY;0@^ zq{4u>@9f#Lk4Z5Ys8<$96#?-opx)!S)G-k-2V^-H$RMbJq*~4eHG_d%2lN5)H&Psd zX8AX$88`tmKFR0LpMR2)lDYX-@nh*(FEV>wX{AjT2oT23$* zkPyFQSx&M8a5#d2T+4}$0s_&&O0MO^WJ8(*W-3I%Z(_12RUCjL5gsSDoS?`7xUw3l zg%w#2Ae7H3aRf7^mQ(5oW@=bYi6aPQLy}84s2Y@7l#+5_6*+|rnj=YcL aAiw|+C&DJQP0oz~0000YQCYi7Ht0k1EPi|MH~WyXp>^jAd0~7hD?*o|7+zUSOmJ?LZ_^s!$gZ3u%7L@{I=7VP% z_a!2<(cVB@qEY}B;TUmjD@L`X^1Yi-;U)bXkac?RG$k<~ew|5Zb_7R-fV{idjgha>@ws{Ht;KCkj zQFSm8?6Kj>9;+mPy8VE^q9>%u-?;9XQRxD|Aiv-xvsxRx6znc_IpYTx zfWH#Qg58aMlJ_bA+ufNEGInWqK?Pv)KiS=j%2b%`t|x@t`LMen0`RADX~rk7pQv06 zWlo5c+MO@257h$(g5A+%H&VMCX1nVMc6aOB?z{=WIWqa27|$1Fc=Jme~K>w1^{LC0JpuQ^5_5n002ov JPDHLkV1f=sN^1ZB literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-xhdpi/ic_action_previous_item.png b/src/android/res/drawable-xhdpi/ic_action_previous_item.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8ac91dec4b072dc40804c4b4178b065f7ed708 GIT binary patch literal 744 zcmVP)0=8N@8J`wu9@%$5uRVrBx8ftW!IF~j%BBb>Mv#M(Z}`;z8v z>q~mycWuAdYfB}CLZMJ76bgkxAqlNkt4qaoJ9|ROcW^o9p9_@XY&P4tXlq{}`3#qf z++S6c<1|elR;$%})B=Ef9ha+ustQU55vIEqArVHtZwa}_Fj()p-9!laGA@@WWkbjU z!sO<7JdXAU71YH0fFF zu}1R+4Vfqf$l2o(x2L9_;L{$1Y>txc{lXr-h!AXdnC+1!y(2?N!-qWv-5#e@*pefB z+2hp3xd0IYlKW(J^?H-V`$EC#{tFG)(%n=#3M_NLk z8A7Tq<^xe@gjj?kd!*svWUE}@34V@t9P5IwAq?PUsz((uHG(SFn?P~L$y-sCAB98M`&=jFYBD)$Dt=Dzu!75(wV*}TUW0~lD*P>x|&Qd7_P_ms8#d44)Bd2C{&(UWzlACh z2h+J7JT|oGD;)@GGOaOW6cZBo;L18>N<)*r-hvhul|7veGMpI|#*EiJJ~Wt%evs#k z`(T)NVD|L*hR@nt*!TS8@Q~!>{+{sRLemKr5utrj6SlHuxN0~$If#cv2)meV+a%%C zt)<5#^dMHnC&XAtwAM4=^N6;oJNO3DF++P6=euvz@Yh2KA?RyjT2C~1{B@8Z!OR?g=u^>1hj z_@^A1Ecxr>f}d?`p480di?4S%CphygyLXa^10xFBaPo&p0z-;&MeJ|;OL4%cX7F_N Kb6Mw<&;$S^P}b@I literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-xxhdpi/ic_action_next_item.png b/src/android/res/drawable-xxhdpi/ic_action_next_item.png new file mode 100644 index 0000000000000000000000000000000000000000..51479d8ddfdeccaf4af263b3420af6d536cab5ab GIT binary patch literal 1021 zcmVAgR7s3xqaChTCq{={$6$9pJ`w$`@*0PUS8yqfTW{_#D)Lv`px@2`t;Arl^M+khT`w(Vfi?T|Mb^%iv_Rq4U1O zfOIC@a@?e^XMrZPbBL#^h9X!@67>gteHBogO**6UmzLwHy0IW`R!G#{y&;LRXM`)O zCKMVkO2&U;;^`1IU_bGc#jS@ts9BAth4Sm0M#j^(r~&EY>6LWKA;S>dAMEJ74Gc(Y z!YU%3wk*Zds)dV`c)F&quOs5A2@|STsw%&fiHxT&EyUBp8A9UeJR+WsPy^D}glmqR zB5)Z;+T*D?1H5MhZiC zVZRAHMcoVw(*}4Z#Ezb-nPFkx05M@hqTWiMP)5xu8p6)P05M@56Hocj5Kpr)z>BA$ zGc1}Ko)HR{AEstl_fJn3par{*9*JP#6~f&!WE2MH{@} zPu)8K%QavW5jQ_;U2Rzyz554|S^pCr#L|i1kBOTavVJxONZbsae#o8e7()^1#BW8$ z&8eoiY1#mZo5Kk4$J*kiSpy_)mM!M?nOPBEy#d}H62}qZo7&t%dkyGE#?84iMxprb zA4J5>mG-!4$^eO*F+=ALh(D1adT`TZkjMa;^z5t5nq)7Ug8Ojn^Q}1Q?&vA zAU+Q*XR%|7mKtDcCPP*RSe(g_jR96?GUQYPc9Ld7%9&3*4{^}Rxw11#*&abG`bQE! z%e_N2>>0-kEH2&S>%BgZ_-mIbnzk(CMR#@g_wFbhH@~`!sc5?(ZxC)y;?Ep7hzT92 r000000000000000004ko_$|Nymn?!0<{Ma600000NkvXXu0mjfBNxJV literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-xxhdpi/ic_action_previous_item.png b/src/android/res/drawable-xxhdpi/ic_action_previous_item.png new file mode 100644 index 0000000000000000000000000000000000000000..bc8ff1241ade1ac7ad40427f4843b84fac947e62 GIT binary patch literal 1038 zcmV+p1o8WcP)mo zA`K~MkRSyq+|VX1EpADX!ubtpX-R2X{)3w|G(jRoT+p~M5_UmDVHa*Pqf8=#jI$FR zx|!vD(&-T1laBYiw{P!uE(8Do000000000000000fJA6En+=QS_547@@6xVYsZ?Gr zm&=)@b9;6G;5yEkAX@suJD;TiC?rQeVpkoe~${><9%@Z1CX%D6eP_#-l>|NT7R)*_qFEX2)Z z3?T74%D6eQ{ChHI;>$@{R;^sR=)*pDC&SwV2=Nay) zB5sa6#7!3lNZjlz4jFifo8}FWxY<|6P1oX^F+jPJff;*?o7Q_kH#l0!kdy(ka5gq> z&b`J>lLkoK>?q>qFeFBb3$tKAEHaSB4yM|O>Vm=-=W@XW7fwVD!p9biEK;}xjpZ62 zLt7q8q6g&I3m&*&f(PFoyy8#?wos z2`=KPn?h|dVXcU#JX@YvkEgCGOTo}PAv_Ple<6<}HMg*P=mGnQr>{fY zL;SMkIfCaWV#A=jh22IE$UmO)E$kCTGFlY*W|X81*f2p8PwUp=sr&Z@Nj$v`zaHkq z#M71}qk9;Tp9we0Ti7)#@idubDPC7tB=yQNiKi@XIik6V6&5Kj%o+tpD=d;TK#Dc^ z7Iw_t(7VWXxCH%2JpI=Si{I}Kn!|wo#nW9YEcg=jnZz&rnL*NT&fT%Xf(P9%R_e-< ze|Iz^H3&S|PExBc@MJ;*DgXcg00000000000001>2!0AM0Ol)zPaPAXwEzGB07*qo IM6N<$f+VNg^Z)<= literal 0 HcmV?d00001 diff --git a/src/android/res/drawable-xxhdpi/ic_action_remove.png b/src/android/res/drawable-xxhdpi/ic_action_remove.png new file mode 100644 index 0000000000000000000000000000000000000000..331c545b8cb07a97ee63cb4f1256d1dba5557a82 GIT binary patch literal 681 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n7ln*978H@y_sX^mF+0u=KlZOu|s+fxN=%TuH_vu-63%O z0bh(`mh@(>)(|r>v-Oq66AmapP&R+I|NHM}^O&8P#J-=iwAdNbrpXGl5g7zooZfe! zXy=~Q^508s)?R;|&#w7(p8l)j$+D}f&aa<&y-Vlf4wmJ z{m;$!KDWm<)IWJYp?`yVy?a)Rgy+7R7WpU3-7WlQo&Q_^@0!|=9dkD{|8ah%BK}}y zUwJzR`>Fc-=T0VbW}WtqPP|eM_FyzT{_JE!R zD)mzsyaca2=@)ZQY+Cdukl9DlG4$UH#$_D~bm}KDB)bJnw~uTvRI)fR-^3w}Mf~Sv z_7fcsRN|EmXe#eGIi0hhi=*$;Ql>v@O{@PLixhm)QT}Dxr&MO1o`9IAr*$3O_1veg z`?Qpqrz1f--tK_DQpAbv90e{c(m#7YGyP$o^P)53LpC?loVb0lEiVH0g|-+>zh76d zzw5&@{nHH0@veKsKFU^vF}CfCD89*-Dfr;V_F3ly55?}QZh7Ijuejxf>%Q!k7oPi) zTVD9?i*9)lxG%WnMd&{7mKTxxoLgSR?yG8<(OmPC!%+O^D#0YbPom04?mSuT+F^Mr zylcW%?L%qnpM8_#v%0PH_*W!j7~dy$w}_iMj&Eb;mhJQVTeVv6|AD73EMLFabmp+t zH{}!WBYrC$Dt@-J@Me4I8>JZ0$#0vg1>-jHzhy>_Y6gY|m493Y3}5QcuZR|#@EIiM M>FVdQ&MBb@07pA8tN;K2 literal 0 HcmV?d00001 diff --git a/www/inappcrossbrowser.js b/www/inappcrossbrowser.js new file mode 100644 index 0000000..bebc268 --- /dev/null +++ b/www/inappcrossbrowser.js @@ -0,0 +1,105 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * +*/ + +var exec = require('cordova/exec'), + channel = require('cordova/channel'), + modulemapper = require('cordova/modulemapper'), + urlutil = require('cordova/urlutil'), + cmd = 'InAppCrossBrowser'; + +function InAppBrowser() { + this.channels = { + 'loadstart': channel.create('loadstart'), + 'loadstop' : channel.create('loadstop'), + 'loaderror' : channel.create('loaderror'), + 'exit' : channel.create('exit') + }; +} + +InAppBrowser.prototype = { + _eventHandler: function (event) { + if (event && (event.type in this.channels)) { + this.channels[event.type].fire(event); + } + }, + close: function (eventname) { + exec(null, null, cmd, "close", []); + }, + show: function (eventname) { + exec(null, null, cmd, "show", []); + }, + addEventListener: function (eventname,f) { + if (eventname in this.channels) { + this.channels[eventname].subscribe(f); + } + }, + removeEventListener: function(eventname, f) { + if (eventname in this.channels) { + this.channels[eventname].unsubscribe(f); + } + }, + + executeScript: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, cmd, "injectScriptCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, cmd, "injectScriptFile", [injectDetails.file, !!cb]); + } else { + throw new Error('executeScript requires exactly one of code or file to be specified'); + } + }, + + insertCSS: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, cmd, "injectStyleCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, cmd, "injectStyleFile", [injectDetails.file, !!cb]); + } else { + throw new Error('insertCSS requires exactly one of code or file to be specified'); + } + } +}; + +module.exports = function(strUrl, strWindowName, strWindowFeatures, callbacks) { + // Don't catch calls that write to existing frames (e.g. named iframes). + if (window.frames && window.frames[strWindowName]) { + var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); + return origOpenFunc.apply(window, arguments); + } + + strUrl = urlutil.makeAbsolute(strUrl); + var iab = new InAppBrowser(); + + callbacks = callbacks || {}; + for (var callbackName in callbacks) { + iab.addEventListener(callbackName, callbacks[callbackName]); + } + + var cb = function(eventname) { + iab._eventHandler(eventname); + }; + + strWindowFeatures = strWindowFeatures || ""; + + exec(cb, cb, cmd, "open", [strUrl, strWindowName, strWindowFeatures]); + return iab; +}; +