diff --git a/framework/res/xml/config.xml b/framework/res/xml/config.xml
index da9c1f46..b6900078 100644
--- a/framework/res/xml/config.xml
+++ b/framework/res/xml/config.xml
@@ -51,6 +51,7 @@
+
diff --git a/framework/src/org/apache/cordova/InAppBrowser.java b/framework/src/org/apache/cordova/InAppBrowser.java
new file mode 100644
index 00000000..9b747d3a
--- /dev/null
+++ b/framework/src/org/apache/cordova/InAppBrowser.java
@@ -0,0 +1,515 @@
+/*
+ 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 org.apache.cordova;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+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;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.WebChromeClient;
+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;
+
+public class InAppBrowser extends CordovaPlugin {
+
+ private static final String NULL = "null";
+ protected static final String LOG_TAG = "InAppBrowser";
+ private static final String SELF = "_self";
+ private static final String SYSTEM = "_system";
+ private static final String BLANK = "_blank";
+ private static final String LOCATION = "location";
+ private static int CLOSE_EVENT = 0;
+ private static int LOCATION_CHANGED_EVENT = 1;
+
+ private String browserCallbackId = null;
+
+ private Dialog dialog;
+ private WebView inAppWebView;
+ private EditText edittext;
+ private boolean showLocationBar = true;
+ private CallbackContext callbackContext;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackId The callback id used when calling back into JavaScript.
+ * @return A PluginResult object with a status and message.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ PluginResult.Status status = PluginResult.Status.OK;
+ String result = "";
+ this.callbackContext = callbackContext;
+
+ try {
+ if (action.equals("open")) {
+ String url = args.getString(0);
+ String target = args.optString(1);
+ if (target == null || target.equals("") || target.equals(NULL)) {
+ target = SELF;
+ }
+ HashMap features = parseFeature(args.optString(2));
+
+ Log.d(LOG_TAG, "target = " + target);
+
+ url = updateUrl(url);
+
+ // SELF
+ if (SELF.equals(target)) {
+ Log.d(LOG_TAG, "in self");
+ // load in webview
+ if (url.startsWith("file://") || url.startsWith("javascript:")
+ || this.webView.isUrlWhiteListed(url)) {
+ this.webView.loadUrl(url);
+ }
+ // load in InAppBrowser
+ else {
+ result = this.showWebPage(url, features);
+ }
+ }
+ // SYSTEM
+ else if (SYSTEM.equals(target)) {
+ Log.d(LOG_TAG, "in system");
+ result = this.openExternal(url);
+ }
+ // BLANK - or anything else
+ else {
+ Log.d(LOG_TAG, "in blank");
+ result = this.showWebPage(url, features);
+ }
+ }
+ else if (action.equals("close")) {
+ closeDialog();
+
+ JSONObject obj = new JSONObject();
+ obj.put("type", CLOSE_EVENT);
+
+ PluginResult pluginResult = new PluginResult(status, obj);
+ pluginResult.setKeepCallback(false);
+ this.callbackContext.sendPluginResult(pluginResult);
+ }
+ else {
+ status = PluginResult.Status.INVALID_ACTION;
+ }
+ this.callbackContext.sendPluginResult(new PluginResult(status, result));
+ } catch (JSONException e) {
+ this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+ }
+ return true;
+ }
+
+ /**
+ * Put the list of features into a hash map
+ *
+ * @param optString
+ * @return
+ */
+ private 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;
+ }
+ }
+
+ /**
+ * Convert relative URL to full path
+ *
+ * @param url
+ * @return
+ */
+ private String updateUrl(String url) {
+ Uri newUrl = Uri.parse(url);
+ if (newUrl.isRelative()) {
+ url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url;
+ }
+ return url;
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param usePhoneGap Load url in PhoneGap webview
+ * @return "" if ok, or error message.
+ */
+ public String openExternal(String url) {
+ try {
+ Intent intent = null;
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ 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
+ */
+ private void closeDialog() {
+ // TODO: fire 'exit' event
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ }
+
+ /**
+ * Checks to see if it is possible to go back one page in history, then does so.
+ */
+ private 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.
+ */
+ private void goForward() {
+ if (this.inAppWebView.canGoForward()) {
+ this.inAppWebView.goForward();
+ }
+ }
+
+ /**
+ * Navigate to the new page
+ *
+ * @param url to load
+ */
+ private 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
+ */
+ private boolean getShowLocationBar() {
+ return this.showLocationBar;
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param jsonObject
+ */
+ public String showWebPage(final String url, HashMap features) {
+ // Determine if we should hide the location bar.
+ showLocationBar = true;
+ if (features != null) {
+ showLocationBar = features.get(LOCATION).booleanValue();
+ }
+
+ // 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;
+ }
+
+ public void run() {
+ // Let's create the main dialog
+ dialog = new Dialog(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.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ public void onDismiss(DialogInterface dialog) {
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", CLOSE_EVENT);
+
+ sendUpdate(obj, false);
+ } catch (JSONException e) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+ });
+
+ // Main container layout
+ LinearLayout main = new LinearLayout(cordova.getActivity());
+ main.setOrientation(LinearLayout.VERTICAL);
+
+ // Toolbar layout
+ RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
+ toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_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.FILL_PARENT);
+ backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
+ back.setLayoutParams(backLayoutParams);
+ back.setContentDescription("Back Button");
+ back.setId(2);
+ back.setText("<");
+ 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.FILL_PARENT);
+ forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
+ forward.setLayoutParams(forwardLayoutParams);
+ forward.setContentDescription("Forward Button");
+ forward.setId(3);
+ forward.setText(">");
+ 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.FILL_PARENT, LayoutParams.FILL_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 button
+ Button close = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT);
+ closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ close.setLayoutParams(closeLayoutParams);
+ forward.setContentDescription("Close Button");
+ close.setId(5);
+ close.setText("Done");
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ closeDialog();
+ }
+ });
+
+ // WebView
+ inAppWebView = new WebView(cordova.getActivity());
+ inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+ inAppWebView.setWebChromeClient(new WebChromeClient());
+ WebViewClient client = new InAppBrowserClient(edittext);
+ inAppWebView.setWebViewClient(client);
+ WebSettings settings = inAppWebView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+ settings.setBuiltInZoomControls(true);
+ settings.setPluginsEnabled(true);
+ settings.setDomStorageEnabled(true);
+ 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);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.copyFrom(dialog.getWindow().getAttributes());
+ lp.width = WindowManager.LayoutParams.FILL_PARENT;
+ lp.height = WindowManager.LayoutParams.FILL_PARENT;
+
+ dialog.setContentView(main);
+ dialog.show();
+ dialog.getWindow().setAttributes(lp);
+ }
+
+ private Bitmap loadDrawable(String filename) throws java.io.IOException {
+ InputStream input = cordova.getActivity().getAssets().open(filename);
+ return BitmapFactory.decodeStream(input);
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ return "";
+ }
+
+ /**
+ * Create a new plugin result and send it back to JavaScript
+ *
+ * @param obj a JSONObject contain event payload information
+ */
+ private void sendUpdate(JSONObject obj, boolean keepCallback) {
+ if (this.browserCallbackId != null) {
+ PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
+ result.setKeepCallback(keepCallback);
+ this.callbackContext.sendPluginResult(result);
+ }
+ }
+
+ /**
+ * The webview client receives notifications about appView
+ */
+ public class InAppBrowserClient extends WebViewClient {
+ EditText edittext;
+
+ /**
+ * Constructor.
+ *
+ * @param mContext
+ * @param edittext
+ */
+ public InAppBrowserClient(EditText mEditText) {
+ 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;
+ } else {
+ newloc = "http://" + url;
+ }
+
+ if (!newloc.equals(edittext.getText().toString())) {
+ edittext.setText(newloc);
+ }
+
+ // TODO: Fire 'loadstart' event
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOCATION_CHANGED_EVENT);
+ obj.put("location", url);
+
+ sendUpdate(obj, true);
+ } catch (JSONException e) {
+ Log.d("InAppBrowser", "This should never happen");
+ }
+ }
+
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ // TODO: Fire 'loadstop' event
+ }
+ }
+}
\ No newline at end of file