You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ma...@apache.org on 2012/11/23 04:29:00 UTC

[11/11] android commit: CB-1508: Implement InAppBrowser feature

CB-1508: Implement InAppBrowser feature

Initial checkin. Need to clean up the UI and add eventing.


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/a87825db
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/a87825db
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/a87825db

Branch: refs/heads/master
Commit: a87825dbeebed27381836c2ee1615e1673055313
Parents: 7657faa
Author: Simon MacDonald <si...@gmail.com>
Authored: Thu Nov 22 12:54:53 2012 -0500
Committer: Simon MacDonald <si...@gmail.com>
Committed: Thu Nov 22 22:21:24 2012 -0500

----------------------------------------------------------------------
 framework/res/xml/config.xml                       |    1 +
 framework/src/org/apache/cordova/InAppBrowser.java |  515 +++++++++++++++
 2 files changed, 516 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a87825db/framework/res/xml/config.xml
----------------------------------------------------------------------
diff --git a/framework/res/xml/config.xml b/framework/res/xml/config.xml
index da9c1f4..b690007 100644
--- a/framework/res/xml/config.xml
+++ b/framework/res/xml/config.xml
@@ -51,6 +51,7 @@
     <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen"/>
     <plugin name="Echo" value="org.apache.cordova.Echo" />
     <plugin name="Globalization" value="org.apache.cordova.Globalization"/>
+    <plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser"/>
 </plugins>
 </cordova>
 

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a87825db/framework/src/org/apache/cordova/InAppBrowser.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/InAppBrowser.java b/framework/src/org/apache/cordova/InAppBrowser.java
new file mode 100644
index 0000000..9b747d3
--- /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<String, Boolean> 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<String, Boolean> parseFeature(String optString) {
+        if (optString.equals(NULL)) {
+            return null;
+        } else {
+            HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+            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<String, Boolean> 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