You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/12/31 05:31:28 UTC

[1/4] android commit: Closing stale pull request: close #114

Repository: cordova-android
Updated Branches:
  refs/heads/4.0.x c6b171ba9 -> 61c4bb988
  refs/heads/master b10fe465a -> 343974664


Closing stale pull request: close #114


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

Branch: refs/heads/4.0.x
Commit: b10fe465ab2a5ce80dcc1f21e4b03cb1a9e9a598
Parents: 480af26
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Dec 30 23:20:32 2014 -0500
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Dec 30 23:20:52 2014 -0500

----------------------------------------------------------------------

----------------------------------------------------------------------



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[4/4] android commit: Merge branch 'master' into 4.0.x (keyboard events via PluginResult)

Posted by ag...@apache.org.
Merge branch 'master' into 4.0.x (keyboard events via PluginResult)

Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java


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

Branch: refs/heads/4.0.x
Commit: 61c4bb98882c3f6058af5b07e8f486065331e396
Parents: c6b171b 3439746
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Dec 30 23:30:42 2014 -0500
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Dec 30 23:30:42 2014 -0500

----------------------------------------------------------------------
 framework/assets/www/cordova.js                 | 69 ++++++++++++--------
 .../src/org/apache/cordova/AndroidWebView.java  |  2 +-
 .../src/org/apache/cordova/CoreAndroid.java     | 30 ++++++++-
 .../cordova/LinearLayoutSoftKeyboardDetect.java | 25 ++++---
 4 files changed, 87 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/61c4bb98/framework/src/org/apache/cordova/AndroidWebView.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/AndroidWebView.java
index f63a6fb,0000000..245fbdb
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/AndroidWebView.java
+++ b/framework/src/org/apache/cordova/AndroidWebView.java
@@@ -1,795 -1,0 +1,795 @@@
 +/*
 +       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.lang.reflect.InvocationTargetException;
 +import java.lang.reflect.Method;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.List;
 +
 +import android.annotation.SuppressLint;
 +import android.annotation.TargetApi;
 +import android.content.BroadcastReceiver;
 +import android.content.Context;
 +import android.content.Intent;
 +import android.content.IntentFilter;
 +import android.content.pm.ApplicationInfo;
 +import android.net.Uri;
 +import android.os.Build;
 +import android.os.Bundle;
 +import android.util.AttributeSet;
 +import android.util.Log;
 +import android.view.Gravity;
 +import android.view.KeyEvent;
 +import android.view.View;
 +import android.view.ViewGroup;
 +import android.view.inputmethod.InputMethodManager;
 +import android.webkit.WebBackForwardList;
 +import android.webkit.WebHistoryItem;
 +import android.webkit.WebChromeClient;
 +import android.webkit.WebSettings;
 +import android.webkit.WebView;
 +import android.webkit.WebSettings.LayoutAlgorithm;
 +import android.webkit.WebViewClient;
 +import android.widget.FrameLayout;
 +
 +
 +/*
 + * This class is our web view.
 + *
 + * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
 + * @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
 + */
 +public class AndroidWebView extends WebView implements CordovaWebView {
 +
 +    public static final String TAG = "AndroidWebView";
 +
 +    private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
 +
 +    PluginManager pluginManager;
 +
 +    private BroadcastReceiver receiver;
 +
 +
 +    /** Activities and other important classes **/
 +    private CordovaInterface cordova;
 +    AndroidWebViewClient viewClient;
 +    private AndroidChromeClient chromeClient;
 +
 +    // Flag to track that a loadUrl timeout occurred
 +    int loadUrlTimeout = 0;
 +
 +    private long lastMenuEventTime = 0;
 +
 +    CordovaBridge bridge;
 +
 +    /** custom view created by the browser (a video player for example) */
 +    private View mCustomView;
 +    private WebChromeClient.CustomViewCallback mCustomViewCallback;
 +
 +    private CordovaResourceApi resourceApi;
 +    private Whitelist internalWhitelist;
 +    private Whitelist externalWhitelist;
 +    private CordovaPreferences preferences;
 +    // The URL passed to loadUrl(), not necessarily the URL of the current page.
 +    String loadedUrl;
 +    
 +    static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
 +            new FrameLayout.LayoutParams(
 +            ViewGroup.LayoutParams.MATCH_PARENT,
 +            ViewGroup.LayoutParams.MATCH_PARENT,
 +            Gravity.CENTER);
 +    
 +    /** Used when created via reflection. */
 +    public AndroidWebView(Context context) {
 +        this(context, null);
 +    }
 +
 +    /** Required to allow view to be used within XML layouts. */
 +    public AndroidWebView(Context context, AttributeSet attrs) {
 +        super(context, attrs);
 +    }
 +
 +    // Use two-phase init so that the control will work with XML layouts.
 +    @Override
 +    public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
 +            Whitelist internalWhitelist, Whitelist externalWhitelist,
 +            CordovaPreferences preferences) {
 +        if (this.cordova != null) {
 +            throw new IllegalStateException();
 +        }
 +        this.cordova = cordova;
 +        this.internalWhitelist = internalWhitelist;
 +        this.externalWhitelist = externalWhitelist;
 +        this.preferences = preferences;
 +
 +        pluginManager = new PluginManager(this, this.cordova, pluginEntries);
 +        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
 +        bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
 +        initWebViewSettings();
-         pluginManager.addService("App", "org.apache.cordova.CoreAndroid");
++        pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
 +        pluginManager.init();
 +        
 +        if (this.viewClient == null) {
 +            setWebViewClient(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ?
 +                new AndroidWebViewClient(cordova, this) :
 +                new IceCreamCordovaWebViewClient(cordova, this));
 +        }
 +        if (this.chromeClient == null) {
 +            setWebChromeClient(new AndroidChromeClient(cordova, this));
 +        }
 +
 +        exposeJsInterface();
 +    }
 +
 +    @SuppressLint("SetJavaScriptEnabled")
 +    @SuppressWarnings("deprecation")
 +    private void initWebViewSettings() {
 +        this.setInitialScale(0);
 +        this.setVerticalScrollBarEnabled(false);
 +        // TODO: The Activity is the one that should call requestFocus().
 +        if (shouldRequestFocusOnInit()) {
 +            this.requestFocusFromTouch();
 +        }
 +        this.setInitialScale(0);
 +        this.setVerticalScrollBarEnabled(false);
 +        if (shouldRequestFocusOnInit()) {
 +            this.requestFocusFromTouch();
 +        }
 +        // Enable JavaScript
 +        final WebSettings settings = this.getSettings();
 +        settings.setJavaScriptEnabled(true);
 +        settings.setJavaScriptCanOpenWindowsAutomatically(true);
 +        settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
 +        
 +        // Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
 +        try {
 +            Method gingerbread_getMethod =  WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
 +            
 +            String manufacturer = android.os.Build.MANUFACTURER;
 +            Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
 +            if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
 +                    android.os.Build.MANUFACTURER.contains("HTC"))
 +            {
 +                gingerbread_getMethod.invoke(settings, true);
 +            }
 +        } catch (NoSuchMethodException e) {
 +            Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
 +        } catch (IllegalArgumentException e) {
 +            Log.d(TAG, "Doing the NavDump failed with bad arguments");
 +        } catch (IllegalAccessException e) {
 +            Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
 +        } catch (InvocationTargetException e) {
 +            Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
 +        }
 +
 +        //We don't save any form data in the application
 +        settings.setSaveFormData(false);
 +        settings.setSavePassword(false);
 +        
 +        // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
 +        // while we do this
 +        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
 +            Level16Apis.enableUniversalAccess(settings);
 +        }
 +        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
 +            Level17Apis.setMediaPlaybackRequiresUserGesture(settings, false);
 +        }
 +        // Enable database
 +        // We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
 +        String databasePath = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
 +        settings.setDatabaseEnabled(true);
 +        settings.setDatabasePath(databasePath);
 +        
 +        
 +        //Determine whether we're in debug or release mode, and turn on Debugging!
 +        ApplicationInfo appInfo = getContext().getApplicationContext().getApplicationInfo();
 +        if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
 +            android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
 +            enableRemoteDebugging();
 +        }
 +        
 +        settings.setGeolocationDatabasePath(databasePath);
 +
 +        // Enable DOM storage
 +        settings.setDomStorageEnabled(true);
 +
 +        // Enable built-in geolocation
 +        settings.setGeolocationEnabled(true);
 +        
 +        // Enable AppCache
 +        // Fix for CB-2282
 +        settings.setAppCacheMaxSize(5 * 1048576);
 +        settings.setAppCachePath(databasePath);
 +        settings.setAppCacheEnabled(true);
 +        
 +        // Fix for CB-1405
 +        // Google issue 4641
 +        settings.getUserAgentString();
 +        
 +        IntentFilter intentFilter = new IntentFilter();
 +        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
 +        if (this.receiver == null) {
 +            this.receiver = new BroadcastReceiver() {
 +                @Override
 +                public void onReceive(Context context, Intent intent) {
 +                    settings.getUserAgentString();
 +                }
 +            };
 +            getContext().registerReceiver(this.receiver, intentFilter);
 +        }
 +        // end CB-1405
 +    }
 +
 +    @TargetApi(Build.VERSION_CODES.KITKAT)
 +    private void enableRemoteDebugging() {
 +        try {
 +            WebView.setWebContentsDebuggingEnabled(true);
 +        } catch (IllegalArgumentException e) {
 +            Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
 +            e.printStackTrace();
 +        }
 +    }
 +
 +	/**
 +	 * Override this method to decide whether or not you need to request the
 +	 * focus when your application start
 +	 * 
 +	 * @return true unless this method is overriden to return a different value
 +	 */
 +    protected boolean shouldRequestFocusOnInit() {
 +		return true;
 +	}
 +
 +    private void exposeJsInterface() {
 +        if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
 +            Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
 +            // Bug being that Java Strings do not get converted to JS strings automatically.
 +            // This isn't hard to work-around on the JS side, but it's easier to just
 +            // use the prompt bridge instead.
 +            return;            
 +        } 
 +        AndroidExposedJsApi exposedJsApi = new AndroidExposedJsApi(bridge);
 +        this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
 +    }
 +
 +    @Override
 +    public void setWebViewClient(WebViewClient client) {
 +        this.viewClient = (AndroidWebViewClient)client;
 +        super.setWebViewClient(client);
 +    }
 +
 +    @Override
 +    public void setWebChromeClient(WebChromeClient client) {
 +        this.chromeClient = (AndroidChromeClient)client;
 +        super.setWebChromeClient(client);
 +    }
 +
 +    /**
 +     * Load the url into the webview.
 +     */
 +    @Override
 +    public void loadUrl(String url) {
 +        this.loadUrlIntoView(url, true);
 +    }
 +
 +    /**
 +     * Load the url into the webview.
 +     */
 +    @Override
 +    public void loadUrlIntoView(final String url, boolean recreatePlugins) {
 +        if (url.equals("about:blank") || url.startsWith("javascript:")) {
 +            this.loadUrlNow(url);
 +            return;
 +        }
 +
 +        LOG.d(TAG, ">>> loadUrl(" + url + ")");
 +        recreatePlugins = recreatePlugins || (loadedUrl == null);
 +
 +        if (recreatePlugins) {
 +            // Don't re-initialize on first load.
 +            if (loadedUrl != null) {
 +                this.pluginManager.init();
 +            }
 +            this.loadedUrl = url;
 +        }
 +
 +        // Create a timeout timer for loadUrl
 +        final AndroidWebView me = this;
 +        final int currentLoadUrlTimeout = me.loadUrlTimeout;
 +        final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
 +
 +        // Timeout error method
 +        final Runnable loadError = new Runnable() {
 +            public void run() {
 +                me.stopLoading();
 +                LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
 +                if (viewClient != null) {
 +                    viewClient.onReceivedError(AndroidWebView.this, -6, "The connection to the server was unsuccessful.", url);
 +                }
 +            }
 +        };
 +
 +        // Timeout timer method
 +        final Runnable timeoutCheck = new Runnable() {
 +            public void run() {
 +                try {
 +                    synchronized (this) {
 +                        wait(loadUrlTimeoutValue);
 +                    }
 +                } catch (InterruptedException e) {
 +                    e.printStackTrace();
 +                }
 +
 +                // If timeout, then stop loading and handle error
 +                if (me.loadUrlTimeout == currentLoadUrlTimeout) {
 +                    me.cordova.getActivity().runOnUiThread(loadError);
 +                }
 +            }
 +        };
 +
 +        // Load url
 +        this.cordova.getActivity().runOnUiThread(new Runnable() {
 +            public void run() {
 +                cordova.getThreadPool().execute(timeoutCheck);
 +                me.loadUrlNow(url);
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Load URL in webview.
 +     */
 +    private void loadUrlNow(String url) {
 +        if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
 +            LOG.d(TAG, ">>> loadUrlNow()");
 +        }
 +        if (url.startsWith("file://") || url.startsWith("javascript:") || internalWhitelist.isUrlWhiteListed(url)) {
 +            super.loadUrl(url);
 +        }
 +    }
 +
 +    @Override
 +    public void stopLoading() {
 +        //viewClient.isCurrentlyLoading = false;
 +        super.stopLoading();
 +    }
 +    
 +    public void onScrollChanged(int l, int t, int oldl, int oldt)
 +    {
 +        super.onScrollChanged(l, t, oldl, oldt);
 +        //We should post a message that the scroll changed
 +        ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
 +        pluginManager.postMessage("onScrollChanged", myEvent);
 +    }
 +    
 +    /**
 +     * Send JavaScript statement back to JavaScript.
 +     * (This is a convenience method)
 +     */
 +    public void sendJavascript(String statement) {
 +        bridge.getMessageQueue().addJavaScript(statement);
 +    }
 +
 +    /**
 +     * Send a plugin result back to JavaScript.
 +     */
 +    public void sendPluginResult(PluginResult result, String callbackId) {
 +        bridge.getMessageQueue().addPluginResult(result, callbackId);
 +    }
 +
 +    /**
 +     * Go to previous page in history.  (We manage our own history)
 +     *
 +     * @return true if we went back, false if we are already at top
 +     */
 +    public boolean backHistory() {
 +
 +        // Check webview first to see if there is a history
 +        // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
 +        if (super.canGoBack()) {
 +            printBackForwardList();
 +            super.goBack();
 +            
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +
 +    /**
 +     * Load the specified URL in the Cordova webview or a new browser instance.
 +     *
 +     * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
 +     *
 +     * @param url           The url to load.
 +     * @param openExternal  Load url in browser instead of Cordova webview.
 +     * @param clearHistory  Clear the history stack, so new page becomes top of history
 +     * @param params        Parameters for new app
 +     */
 +    public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
 +        LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
 +
 +        // If clearing history
 +        if (clearHistory) {
 +            this.clearHistory();
 +        }
 +
 +        // If loading into our webview
 +        if (!openExternal) {
 +
 +            // Make sure url is in whitelist
 +            if (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(url)) {
 +                // TODO: What about params?
 +                // Load new URL
 +                loadUrlIntoView(url, true);
 +                return;
 +            }
 +            // Load in default viewer if not
 +            LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list.  Loading into browser instead. (URL=" + url + ")");
 +        }
 +        try {
 +            // 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.
 +            Intent intent = new Intent(Intent.ACTION_VIEW);
 +            Uri uri = Uri.parse(url);
 +            if ("file".equals(uri.getScheme())) {
 +                intent.setDataAndType(uri, resourceApi.getMimeType(uri));
 +            } else {
 +                intent.setData(uri);
 +            }
 +            cordova.getActivity().startActivity(intent);
 +        } catch (android.content.ActivityNotFoundException e) {
 +            LOG.e(TAG, "Error loading url " + url, e);
 +        }
 +    }
 +
 +    /*
 +     * onKeyDown
 +     */
 +    @Override
 +    public boolean onKeyDown(int keyCode, KeyEvent event)
 +    {
 +        if(boundKeyCodes.contains(keyCode))
 +        {
 +            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
 +                    return true;
 +            }
 +            else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
 +                    return true;
 +            }
 +            else
 +            {
 +                return super.onKeyDown(keyCode, event);
 +            }
 +        }
 +        else if(keyCode == KeyEvent.KEYCODE_BACK)
 +        {
 +            return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
 +
 +        }
 +        else if(keyCode == KeyEvent.KEYCODE_MENU)
 +        {
 +            //How did we get here?  Is there a childView?
 +            View childView = this.getFocusedChild();
 +            if(childView != null)
 +            {
 +                //Make sure we close the keyboard if it's present
 +                InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
 +                imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
 +                cordova.getActivity().openOptionsMenu();
 +                return true;
 +            } else {
 +                return super.onKeyDown(keyCode, event);
 +            }
 +        }
 +        return super.onKeyDown(keyCode, event);
 +    }
 +
 +    @Override
 +    public boolean onKeyUp(int keyCode, KeyEvent event)
 +    {
 +        // If back key
 +        if (keyCode == KeyEvent.KEYCODE_BACK) {
 +            // A custom view is currently displayed  (e.g. playing a video)
 +            if(mCustomView != null) {
 +                this.hideCustomView();
 +                return true;
 +            } else {
 +                // The webview is currently displayed
 +                // If back key is bound, then send event to JavaScript
 +                if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
 +                    return true;
 +                } else {
 +                    // If not bound
 +                    // Go to previous page in webview if it is possible to go back
 +                    if (this.backHistory()) {
 +                        return true;
 +                    }
 +                    // If not, then invoke default behavior
 +                }
 +            }
 +        }
 +        // Legacy
 +        else if (keyCode == KeyEvent.KEYCODE_MENU) {
 +            if (this.lastMenuEventTime < event.getEventTime()) {
 +                this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
 +            }
 +            this.lastMenuEventTime = event.getEventTime();
 +            return super.onKeyUp(keyCode, event);
 +        }
 +        // If search key
 +        else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
 +            this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
 +            return true;
 +        }
 +
 +        //Does webkit change this behavior?
 +        return super.onKeyUp(keyCode, event);
 +    }
 +
 +    @Override
 +    public void setButtonPlumbedToJs(int keyCode, boolean override) {
 +        switch (keyCode) {
 +            case KeyEvent.KEYCODE_VOLUME_DOWN:
 +            case KeyEvent.KEYCODE_VOLUME_UP:
 +            case KeyEvent.KEYCODE_BACK:
 +                // TODO: Why are search and menu buttons handled separately?
 +                if (override) {
 +                    boundKeyCodes.add(keyCode);
 +                } else {
 +                    boundKeyCodes.remove(keyCode);
 +                }
 +                return;
 +            default:
 +                throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
 +        }
 +    }
 +
 +    @Override
 +    public boolean isButtonPlumbedToJs(int keyCode)
 +    {
 +        return boundKeyCodes.contains(keyCode);
 +    }
 +
 +    public void handlePause(boolean keepRunning)
 +    {
 +        LOG.d(TAG, "Handle the pause");
 +        // Send pause event to JavaScript
 +        this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
 +
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onPause(keepRunning);
 +        }
 +
 +        // If app doesn't want to run in background
 +        if (!keepRunning) {
 +            // Pause JavaScript timers (including setInterval)
 +            this.pauseTimers();
 +        }
 +    }
 +    
 +    public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
 +    {
 +        this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
 +        
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onResume(keepRunning);
 +        }
 +
 +        // Resume JavaScript timers (including setInterval)
 +        this.resumeTimers();
 +    }
 +    
 +    public void handleDestroy()
 +    {
 +        // Cancel pending timeout timer.
 +        loadUrlTimeout++;
 +
 +        // Send destroy event to JavaScript
 +        this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
 +
 +        // Load blank page so that JavaScript onunload is called
 +        this.loadUrl("about:blank");
 +        
 +        //Remove last AlertDialog
 +        this.chromeClient.destroyLastDialog();
 +
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onDestroy();
 +        }
 +        
 +        // unregister the receiver
 +        if (this.receiver != null) {
 +            try {
 +                getContext().unregisterReceiver(this.receiver);
 +            } catch (Exception e) {
 +                Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
 +            }
 +        }
 +    }
 +    
 +    public void onNewIntent(Intent intent)
 +    {
 +        //Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onNewIntent(intent);
 +        }
 +    }
 +    
 +    // Wrapping these functions in their own class prevents warnings in adb like:
 +    // VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
 +    @TargetApi(16)
 +    private static class Level16Apis {
 +        static void enableUniversalAccess(WebSettings settings) {
 +            settings.setAllowUniversalAccessFromFileURLs(true);
 +        }
 +    }
 +
 +    @TargetApi(17)
 +    private static final class Level17Apis {
 +        static void setMediaPlaybackRequiresUserGesture(WebSettings settings, boolean value) {
 +            settings.setMediaPlaybackRequiresUserGesture(value);
 +        }
 +    }
 +    public void printBackForwardList() {
 +        WebBackForwardList currentList = this.copyBackForwardList();
 +        int currentSize = currentList.getSize();
 +        for(int i = 0; i < currentSize; ++i)
 +        {
 +            WebHistoryItem item = currentList.getItemAtIndex(i);
 +            String url = item.getUrl();
 +            LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
 +        }
 +    }
 +    
 +    
 +    //Can Go Back is BROKEN!
 +    public boolean startOfHistory()
 +    {
 +        WebBackForwardList currentList = this.copyBackForwardList();
 +        WebHistoryItem item = currentList.getItemAtIndex(0);
 +        if( item!=null){	// Null-fence in case they haven't called loadUrl yet (CB-2458)
 +	        String url = item.getUrl();
 +	        String currentUrl = this.getUrl();
 +	        LOG.d(TAG, "The current URL is: " + currentUrl);
 +	        LOG.d(TAG, "The URL at item 0 is: " + url);
 +	        return currentUrl.equals(url);
 +        }
 +        return false;
 +    }
 +
 +    public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
 +        // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
 +        Log.d(TAG, "showing Custom View");
 +        // if a view already exists then immediately terminate the new one
 +        if (mCustomView != null) {
 +            callback.onCustomViewHidden();
 +            return;
 +        }
 +        
 +        // Store the view and its callback for later (to kill it properly)
 +        mCustomView = view;
 +        mCustomViewCallback = callback;
 +        
 +        // Add the custom view to its container.
 +        ViewGroup parent = (ViewGroup) this.getParent();
 +        parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
 +        
 +        // Hide the content view.
 +        this.setVisibility(View.GONE);
 +        
 +        // Finally show the custom view container.
 +        parent.setVisibility(View.VISIBLE);
 +        parent.bringToFront();
 +    }
 +
 +    public void hideCustomView() {
 +        // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
 +        Log.d(TAG, "Hiding Custom View");
 +        if (mCustomView == null) return;
 +
 +        // Hide the custom view.
 +        mCustomView.setVisibility(View.GONE);
 +        
 +        // Remove the custom view from its container.
 +        ViewGroup parent = (ViewGroup) this.getParent();
 +        parent.removeView(mCustomView);
 +        mCustomView = null;
 +        mCustomViewCallback.onCustomViewHidden();
 +        
 +        // Show the content view.
 +        this.setVisibility(View.VISIBLE);
 +    }
 +    
 +    /**
 +     * if the video overlay is showing then we need to know 
 +     * as it effects back button handling
 +     * 
 +     * @return true if custom view is showing
 +     */
 +    public boolean isCustomViewShowing() {
 +        return mCustomView != null;
 +    }
 +    
 +    public WebBackForwardList restoreState(Bundle savedInstanceState)
 +    {
 +        WebBackForwardList myList = super.restoreState(savedInstanceState);
 +        Log.d(TAG, "WebView restoration crew now restoring!");
 +        //Initialize the plugin manager once more
 +        this.pluginManager.init();
 +        return myList;
 +    }
 +
 +    public CordovaResourceApi getResourceApi() {
 +        return resourceApi;
 +    }
 +
 +    void onPageReset() {
 +        boundKeyCodes.clear();
 +        pluginManager.onReset();
 +        bridge.reset(loadedUrl);
 +    }
 +
 +    @Override
 +    public PluginManager getPluginManager() {
 +        return this.pluginManager;
 +    }
 +
 +    @Override
 +    public View getView() {
 +        return this;
 +    }
 +
 +    @Override
 +    public Whitelist getWhitelist() {
 +        return this.internalWhitelist;
 +    }
 +
 +    @Override
 +    public Whitelist getExternalWhitelist() {
 +        return this.externalWhitelist;
 +    }
 +
 +    @Override
 +    public CordovaPreferences getPreferences() {
 +        return preferences;
 +    }
 +
 +    @Override
 +    public void onFilePickerResult(Uri uri) {
 +        if (null == chromeClient.mUploadMessage)
 +            return;
 +        chromeClient.mUploadMessage.onReceiveValue(uri);
 +        chromeClient.mUploadMessage = null;
 +    }
 +    
 +    @Override
 +    public Object postMessage(String id, Object data) {
 +        return pluginManager.postMessage(id, data);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/61c4bb98/framework/src/org/apache/cordova/CoreAndroid.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/CoreAndroid.java
index 5354ddc,0000000..a11c6fc
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/CoreAndroid.java
+++ b/framework/src/org/apache/cordova/CoreAndroid.java
@@@ -1,301 -1,0 +1,329 @@@
 +/*
 +       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 org.apache.cordova.CallbackContext;
 +import org.apache.cordova.CordovaPlugin;
 +import org.apache.cordova.LOG;
 +import org.apache.cordova.PluginResult;
 +import org.json.JSONArray;
 +import org.json.JSONException;
 +import org.json.JSONObject;
 +
 +import android.content.BroadcastReceiver;
 +import android.content.Context;
 +import android.content.Intent;
 +import android.content.IntentFilter;
 +import android.telephony.TelephonyManager;
 +import android.view.KeyEvent;
 +
 +import java.util.HashMap;
 +
 +/**
 + * This class exposes methods in Cordova that can be called from JavaScript.
 + */
 +public class CoreAndroid extends CordovaPlugin {
 +
++    public static final String PLUGIN_NAME = "CoreAndroid";
 +    protected static final String TAG = "CordovaApp";
 +    private BroadcastReceiver telephonyReceiver;
++    private CallbackContext messageChannel;
++
++    /**
++     * Send an event to be fired on the Javascript side.
++     *
++     * @param action The name of the event to be fired
++     */
++    public void fireJavascriptEvent(String action) {
++        sendEventMessage(action);
++    }
 +
 +    /**
 +     * Sets the context of the Command. This can then be used to do things like
 +     * get file paths associated with the Activity.
 +     */
 +    @Override
 +    public void pluginInitialize() {
 +        this.initTelephonyReceiver();
 +    }
 +
 +    /**
 +     * Executes the request and returns PluginResult.
 +     *
 +     * @param action            The action to execute.
 +     * @param args              JSONArry of arguments for the plugin.
 +     * @param callbackContext   The callback context from which we were invoked.
 +     * @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 = "";
 +
 +        try {
 +            if (action.equals("clearCache")) {
 +                this.clearCache();
 +            }
 +            else if (action.equals("show")) {
 +                // This gets called from JavaScript onCordovaReady to show the webview.
 +                // I recommend we change the name of the Message as spinner/stop is not
 +                // indicative of what this actually does (shows the webview).
 +                cordova.getActivity().runOnUiThread(new Runnable() {
 +                    public void run() {
 +                        webView.getPluginManager().postMessage("spinner", "stop");
 +                    }
 +                });
 +            }
 +            else if (action.equals("loadUrl")) {
 +                this.loadUrl(args.getString(0), args.optJSONObject(1));
 +            }
 +            else if (action.equals("cancelLoadUrl")) {
 +                //this.cancelLoadUrl();
 +            }
 +            else if (action.equals("clearHistory")) {
 +                this.clearHistory();
 +            }
 +            else if (action.equals("backHistory")) {
 +                this.backHistory();
 +            }
 +            else if (action.equals("overrideButton")) {
 +                this.overrideButton(args.getString(0), args.getBoolean(1));
 +            }
 +            else if (action.equals("overrideBackbutton")) {
 +                this.overrideBackbutton(args.getBoolean(0));
 +            }
 +            else if (action.equals("exitApp")) {
 +                this.exitApp();
 +            }
++			else if (action.equals("messageChannel")) {
++                messageChannel = callbackContext;
++                return true;
++            }
++
 +            callbackContext.sendPluginResult(new PluginResult(status, result));
 +            return true;
 +        } catch (JSONException e) {
 +            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
 +            return false;
 +        }
 +    }
 +
 +    //--------------------------------------------------------------------------
 +    // LOCAL METHODS
 +    //--------------------------------------------------------------------------
 +
 +    /**
 +     * Clear the resource cache.
 +     */
 +    public void clearCache() {
 +        cordova.getActivity().runOnUiThread(new Runnable() {
 +            public void run() {
 +                webView.clearCache(true);
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Load the url into the webview.
 +     *
 +     * @param url
 +     * @param props			Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
 +     * @throws JSONException
 +     */
 +    public void loadUrl(String url, JSONObject props) throws JSONException {
 +        LOG.d("App", "App.loadUrl("+url+","+props+")");
 +        int wait = 0;
 +        boolean openExternal = false;
 +        boolean clearHistory = false;
 +
 +        // If there are properties, then set them on the Activity
 +        HashMap<String, Object> params = new HashMap<String, Object>();
 +        if (props != null) {
 +            JSONArray keys = props.names();
 +            for (int i = 0; i < keys.length(); i++) {
 +                String key = keys.getString(i);
 +                if (key.equals("wait")) {
 +                    wait = props.getInt(key);
 +                }
 +                else if (key.equalsIgnoreCase("openexternal")) {
 +                    openExternal = props.getBoolean(key);
 +                }
 +                else if (key.equalsIgnoreCase("clearhistory")) {
 +                    clearHistory = props.getBoolean(key);
 +                }
 +                else {
 +                    Object value = props.get(key);
 +                    if (value == null) {
 +
 +                    }
 +                    else if (value.getClass().equals(String.class)) {
 +                        params.put(key, (String)value);
 +                    }
 +                    else if (value.getClass().equals(Boolean.class)) {
 +                        params.put(key, (Boolean)value);
 +                    }
 +                    else if (value.getClass().equals(Integer.class)) {
 +                        params.put(key, (Integer)value);
 +                    }
 +                }
 +            }
 +        }
 +
 +        // If wait property, then delay loading
 +
 +        if (wait > 0) {
 +            try {
 +                synchronized(this) {
 +                    this.wait(wait);
 +                }
 +            } catch (InterruptedException e) {
 +                e.printStackTrace();
 +            }
 +        }
 +        this.webView.showWebPage(url, openExternal, clearHistory, params);
 +    }
 +
 +    /**
 +     * Clear page history for the app.
 +     */
 +    public void clearHistory() {
 +        cordova.getActivity().runOnUiThread(new Runnable() {
 +            public void run() {
 +                webView.clearHistory();
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Go to previous page displayed.
 +     * This is the same as pressing the backbutton on Android device.
 +     */
 +    public void backHistory() {
 +        cordova.getActivity().runOnUiThread(new Runnable() {
 +            public void run() {
 +                webView.backHistory();
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Override the default behavior of the Android back button.
 +     * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
 +     *
 +     * @param override		T=override, F=cancel override
 +     */
 +    public void overrideBackbutton(boolean override) {
 +        LOG.i("App", "WARNING: Back Button Default Behavior will be overridden.  The backbutton event will be fired!");
 +        webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
 +    }
 +
 +    /**
 +     * Override the default behavior of the Android volume buttons.
 +     * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
 +     *
 +     * @param button        volumeup, volumedown
 +     * @param override      T=override, F=cancel override
 +     */
 +    public void overrideButton(String button, boolean override) {
 +        LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden.  The volume event will be fired!");
 +        if (button.equals("volumeup")) {
 +            webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
 +        }
 +        else if (button.equals("volumedown")) {
 +            webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
 +        }
 +    }
 +
 +    /**
 +     * Return whether the Android back button is overridden by the user.
 +     *
 +     * @return boolean
 +     */
 +    public boolean isBackbuttonOverridden() {
 +        return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
 +    }
 +
 +    /**
 +     * Exit the Android application.
 +     */
 +    public void exitApp() {
 +        this.webView.getPluginManager().postMessage("exit", null);
 +    }
-     
++
 +
 +    /**
 +     * Listen for telephony events: RINGING, OFFHOOK and IDLE
 +     * Send these events to all plugins using
 +     *      CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
 +     */
 +    private void initTelephonyReceiver() {
 +        IntentFilter intentFilter = new IntentFilter();
 +        intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
 +        //final CordovaInterface mycordova = this.cordova;
 +        this.telephonyReceiver = new BroadcastReceiver() {
 +
 +            @Override
 +            public void onReceive(Context context, Intent intent) {
 +
 +                // If state has changed
 +                if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
 +                    if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
 +                        String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
 +                        if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
 +                            LOG.i(TAG, "Telephone RINGING");
 +                            webView.getPluginManager().postMessage("telephone", "ringing");
 +                        }
 +                        else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
 +                            LOG.i(TAG, "Telephone OFFHOOK");
 +                            webView.getPluginManager().postMessage("telephone", "offhook");
 +                        }
 +                        else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
 +                            LOG.i(TAG, "Telephone IDLE");
 +                            webView.getPluginManager().postMessage("telephone", "idle");
 +                        }
 +                    }
 +                }
 +            }
 +        };
 +
 +        // Register the receiver
 +        webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
 +    }
 +
++    private void sendEventMessage(String action) {
++        JSONObject obj = new JSONObject();
++        try {
++            obj.put("action", action);
++        } catch (JSONException e) {
++            LOG.e(TAG, "Failed to create event message", e);
++        }
++        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
++        pluginResult.setKeepCallback(true);
++        messageChannel.sendPluginResult(pluginResult);
++    }
++
 +    /*
 +     * Unregister the receiver
 +     *
 +     */
 +    public void onDestroy()
 +    {
 +        webView.getContext().unregisterReceiver(this.telephonyReceiver);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/61c4bb98/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
index bb6b6a8,6a8a2e3..6b04155
--- a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
+++ b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
@@@ -36,6 -33,7 +33,7 @@@ public class LinearLayoutSoftKeyboardDe
      private int screenWidth = 0;
      private int screenHeight = 0;
      private CordovaActivity app = null;
 -    private App appPlugin = null;
++    private CoreAndroid appPlugin = null;
  
      public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
          super(context);
@@@ -50,7 -48,7 +48,7 @@@
       * gets smaller fire a show keyboard event and when height gets bigger fire
       * a hide keyboard event.
       *
-      * Note: We are using app.postMessage so that this is more compatible with the API
 -     * Note: We are using the core App plugin to send events over the bridge to Javascript
++     * Note: We are using the CoreAndroid plugin to send events over the bridge to Javascript
       *
       * @param widthMeasureSpec
       * @param heightMeasureSpec
@@@ -102,4 -98,15 +98,15 @@@
          oldWidth = width;
      }
  
+     private void sendEvent(String event) {
+         if (appPlugin == null) {
 -            appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME);
++            appPlugin = (CoreAndroid)app.appView.getPluginManager().getPlugin(CoreAndroid.PLUGIN_NAME);
+         }
+ 
+         if (appPlugin == null) {
+             LOG.w(TAG, "Unable to fire event without existing plugin");
+             return;
+         }
+         appPlugin.fireJavascriptEvent(event);
+     }
  }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[2/4] android commit: CB-8210 Use PluginResult instead of sendJavascript() for keyboard events (close #142)

Posted by ag...@apache.org.
CB-8210 Use PluginResult instead of sendJavascript() for keyboard events (close #142)

- Initialize a message channel for native -> Javascript in the core App plugin
- Change keyboard detection to send events via plugin message channel, instead
  using eval() (i.e. webView.sendJavascript())


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

Branch: refs/heads/4.0.x
Commit: 3439746645df1678bcd8e790ca51689627826fbb
Parents: b10fe46
Author: Jason Chase <ja...@gmail.com>
Authored: Wed Dec 24 12:33:31 2014 -0500
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Dec 30 23:25:56 2014 -0500

----------------------------------------------------------------------
 framework/assets/www/cordova.js                 | 69 ++++++++++++--------
 framework/src/org/apache/cordova/App.java       | 30 ++++++++-
 .../src/org/apache/cordova/CordovaWebView.java  |  2 +-
 .../cordova/LinearLayoutSoftKeyboardDetect.java | 25 ++++---
 4 files changed, 87 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/assets/www/cordova.js
----------------------------------------------------------------------
diff --git a/framework/assets/www/cordova.js b/framework/assets/www/cordova.js
index 73805b3..0fdae04 100644
--- a/framework/assets/www/cordova.js
+++ b/framework/assets/www/cordova.js
@@ -1,5 +1,5 @@
 // Platform: android
-// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
+// 12489aed3d93ecc98adfc2091fe566067f2d39fc
 /*
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
@@ -509,9 +509,14 @@ function each(objects, func, context) {
 
 function clobber(obj, key, value) {
     exports.replaceHookForTesting(obj, key);
-    obj[key] = value;
+    var needsProperty = false;
+    try {
+        obj[key] = value;
+    } catch (e) {
+        needsProperty = true;
+    }
     // Getters can only be overridden by getters.
-    if (obj[key] !== value) {
+    if (needsProperty || obj[key] !== value) {
         utils.defineGetter(obj, key, function() {
             return value;
         });
@@ -1027,12 +1032,7 @@ function buildPayload(payload, message) {
         payload.push(+message.slice(1));
     } else if (payloadKind == 'A') {
         var data = message.slice(1);
-        var bytes = window.atob(data);
-        var arraybuffer = new Uint8Array(bytes.length);
-        for (var i = 0; i < bytes.length; i++) {
-            arraybuffer[i] = bytes.charCodeAt(i);
-        }
-        payload.push(arraybuffer.buffer);
+        payload.push(base64.toArrayBuffer(data));
     } else if (payloadKind == 'S') {
         payload.push(window.atob(message.slice(1)));
     } else if (payloadKind == 'M') {
@@ -1167,6 +1167,7 @@ var cordova = require('cordova');
 var modulemapper = require('cordova/modulemapper');
 var platform = require('cordova/platform');
 var pluginloader = require('cordova/pluginloader');
+var utils = require('cordova/utils');
 
 var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
 
@@ -1198,21 +1199,19 @@ function replaceNavigator(origNavigator) {
         for (var key in origNavigator) {
             if (typeof origNavigator[key] == 'function') {
                 newNavigator[key] = origNavigator[key].bind(origNavigator);
-            } else {
+            }
+            else {
                 (function(k) {
-                        Object.defineProperty(newNavigator, k, {
-                            get: function() {
-                                return origNavigator[k];
-                            },
-                            configurable: true,
-                            enumerable: true
-                        });
-                    })(key);
+                    utils.defineGetterSetter(newNavigator,key,function() {
+                        return origNavigator[k];
+                    });
+                })(key);
             }
         }
     }
     return newNavigator;
 }
+
 if (window.navigator) {
     window.navigator = replaceNavigator(window.navigator);
 }
@@ -1293,6 +1292,7 @@ define("cordova/init_b", function(require, exports, module) {
 var channel = require('cordova/channel');
 var cordova = require('cordova');
 var platform = require('cordova/platform');
+var utils = require('cordova/utils');
 
 var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
 
@@ -1327,16 +1327,13 @@ function replaceNavigator(origNavigator) {
         for (var key in origNavigator) {
             if (typeof origNavigator[key] == 'function') {
                 newNavigator[key] = origNavigator[key].bind(origNavigator);
-            } else {
+            }
+            else {
                 (function(k) {
-                        Object.defineProperty(newNavigator, k, {
-                            get: function() {
-                                return origNavigator[k];
-                            },
-                            configurable: true,
-                            enumerable: true
-                        });
-                    })(key);
+                    utils.defineGetterSetter(newNavigator,key,function() {
+                        return origNavigator[k];
+                    });
+                })(key);
             }
         }
     }
@@ -1385,7 +1382,7 @@ platform.bootstrap && platform.bootstrap();
  * Create all cordova objects once native side is ready.
  */
 channel.join(function() {
-    
+
     platform.initialize && platform.initialize();
 
     // Fire event to notify that all objects are created
@@ -1546,11 +1543,27 @@ module.exports = {
         // Let native code know we are all done on the JS side.
         // Native code will then un-hide the WebView.
         channel.onCordovaReady.subscribe(function() {
+            exec(onMessageFromNative, null, 'App', 'messageChannel', []);
             exec(null, null, "App", "show", []);
         });
     }
 };
 
+function onMessageFromNative(msg) {
+    var cordova = require('cordova');
+    var action = msg.action;
+
+    switch (action)
+    {
+        case 'hidekeyboard':
+        case 'showkeyboard':
+            cordova.fireDocumentEvent(action);
+            break;
+        default:
+            throw new Error('Unknown event action ' + msg.action);
+    }
+}
+
 });
 
 // file: src/android/plugin/android/app.js

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/App.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java
index 9e8c1fe..6affb3d 100755
--- a/framework/src/org/apache/cordova/App.java
+++ b/framework/src/org/apache/cordova/App.java
@@ -41,8 +41,19 @@ import java.util.HashMap;
  */
 public class App extends CordovaPlugin {
 
+    public static final String PLUGIN_NAME = "App";
     protected static final String TAG = "CordovaApp";
     private BroadcastReceiver telephonyReceiver;
+    private CallbackContext messageChannel;
+
+    /**
+     * Send an event to be fired on the Javascript side.
+     *
+     * @param action The name of the event to be fired
+     */
+    public void fireJavascriptEvent(String action) {
+        sendEventMessage(action);
+    }
 
     /**
      * Sets the context of the Command. This can then be used to do things like
@@ -100,6 +111,11 @@ public class App extends CordovaPlugin {
             else if (action.equals("exitApp")) {
                 this.exitApp();
             }
+			else if (action.equals("messageChannel")) {
+                messageChannel = callbackContext;
+                return true;
+            }
+
             callbackContext.sendPluginResult(new PluginResult(status, result));
             return true;
         } catch (JSONException e) {
@@ -249,7 +265,7 @@ public class App extends CordovaPlugin {
     public void exitApp() {
         this.webView.postMessage("exit", null);
     }
-    
+
 
     /**
      * Listen for telephony events: RINGING, OFFHOOK and IDLE
@@ -290,6 +306,18 @@ public class App extends CordovaPlugin {
         webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
     }
 
+    private void sendEventMessage(String action) {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("action", action);
+        } catch (JSONException e) {
+            LOG.e(TAG, "Failed to create event message", e);
+        }
+        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
+        pluginResult.setKeepCallback(true);
+        messageChannel.sendPluginResult(pluginResult);
+    }
+
     /*
      * Unregister the receiver
      *

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/CordovaWebView.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java
index feec5a9..7c10e76 100755
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -155,7 +155,7 @@ public class CordovaWebView extends WebView {
         bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
         resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
 
-        pluginManager.addService("App", "org.apache.cordova.App");
+        pluginManager.addService(App.PLUGIN_NAME, "org.apache.cordova.App");
         // This will be removed in 4.0.x in favour of the plugin not being bundled.
         pluginManager.addService(new PluginEntry("SplashScreenInternal", "org.apache.cordova.SplashScreenInternal", true));
         pluginManager.init();

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
index bb6b6a8..6a8a2e3 100755
--- a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
+++ b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
@@ -18,10 +18,7 @@
 */
 package org.apache.cordova;
 
-import org.apache.cordova.LOG;
-
 import android.content.Context;
-//import android.view.View.MeasureSpec;
 import android.widget.LinearLayout;
 
 /**
@@ -36,6 +33,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
     private int screenWidth = 0;
     private int screenHeight = 0;
     private CordovaActivity app = null;
+    private App appPlugin = null;
 
     public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
         super(context);
@@ -50,7 +48,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
      * gets smaller fire a show keyboard event and when height gets bigger fire
      * a hide keyboard event.
      *
-     * Note: We are using app.postMessage so that this is more compatible with the API
+     * Note: We are using the core App plugin to send events over the bridge to Javascript
      *
      * @param widthMeasureSpec
      * @param heightMeasureSpec
@@ -87,14 +85,12 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
         // If the height as gotten bigger then we will assume the soft keyboard has
         // gone away.
         else if (height > oldHeight) {
-            if (app != null)
-                app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
+            sendEvent("hidekeyboard");
         }
-        // If the height as gotten smaller then we will assume the soft keyboard has 
+        // If the height as gotten smaller then we will assume the soft keyboard has
         // been displayed.
         else if (height < oldHeight) {
-            if (app != null)
-                app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
+            sendEvent("showkeyboard");
         }
 
         // Update the old height for the next event
@@ -102,4 +98,15 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
         oldWidth = width;
     }
 
+    private void sendEvent(String event) {
+        if (appPlugin == null) {
+            appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME);
+        }
+
+        if (appPlugin == null) {
+            LOG.w(TAG, "Unable to fire event without existing plugin");
+            return;
+        }
+        appPlugin.fireJavascriptEvent(event);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[3/4] android commit: CB-8210 Use PluginResult instead of sendJavascript() for keyboard events (close #142)

Posted by ag...@apache.org.
CB-8210 Use PluginResult instead of sendJavascript() for keyboard events (close #142)

- Initialize a message channel for native -> Javascript in the core App plugin
- Change keyboard detection to send events via plugin message channel, instead
  using eval() (i.e. webView.sendJavascript())


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

Branch: refs/heads/master
Commit: 3439746645df1678bcd8e790ca51689627826fbb
Parents: b10fe46
Author: Jason Chase <ja...@gmail.com>
Authored: Wed Dec 24 12:33:31 2014 -0500
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Dec 30 23:25:56 2014 -0500

----------------------------------------------------------------------
 framework/assets/www/cordova.js                 | 69 ++++++++++++--------
 framework/src/org/apache/cordova/App.java       | 30 ++++++++-
 .../src/org/apache/cordova/CordovaWebView.java  |  2 +-
 .../cordova/LinearLayoutSoftKeyboardDetect.java | 25 ++++---
 4 files changed, 87 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/assets/www/cordova.js
----------------------------------------------------------------------
diff --git a/framework/assets/www/cordova.js b/framework/assets/www/cordova.js
index 73805b3..0fdae04 100644
--- a/framework/assets/www/cordova.js
+++ b/framework/assets/www/cordova.js
@@ -1,5 +1,5 @@
 // Platform: android
-// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
+// 12489aed3d93ecc98adfc2091fe566067f2d39fc
 /*
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
@@ -509,9 +509,14 @@ function each(objects, func, context) {
 
 function clobber(obj, key, value) {
     exports.replaceHookForTesting(obj, key);
-    obj[key] = value;
+    var needsProperty = false;
+    try {
+        obj[key] = value;
+    } catch (e) {
+        needsProperty = true;
+    }
     // Getters can only be overridden by getters.
-    if (obj[key] !== value) {
+    if (needsProperty || obj[key] !== value) {
         utils.defineGetter(obj, key, function() {
             return value;
         });
@@ -1027,12 +1032,7 @@ function buildPayload(payload, message) {
         payload.push(+message.slice(1));
     } else if (payloadKind == 'A') {
         var data = message.slice(1);
-        var bytes = window.atob(data);
-        var arraybuffer = new Uint8Array(bytes.length);
-        for (var i = 0; i < bytes.length; i++) {
-            arraybuffer[i] = bytes.charCodeAt(i);
-        }
-        payload.push(arraybuffer.buffer);
+        payload.push(base64.toArrayBuffer(data));
     } else if (payloadKind == 'S') {
         payload.push(window.atob(message.slice(1)));
     } else if (payloadKind == 'M') {
@@ -1167,6 +1167,7 @@ var cordova = require('cordova');
 var modulemapper = require('cordova/modulemapper');
 var platform = require('cordova/platform');
 var pluginloader = require('cordova/pluginloader');
+var utils = require('cordova/utils');
 
 var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady];
 
@@ -1198,21 +1199,19 @@ function replaceNavigator(origNavigator) {
         for (var key in origNavigator) {
             if (typeof origNavigator[key] == 'function') {
                 newNavigator[key] = origNavigator[key].bind(origNavigator);
-            } else {
+            }
+            else {
                 (function(k) {
-                        Object.defineProperty(newNavigator, k, {
-                            get: function() {
-                                return origNavigator[k];
-                            },
-                            configurable: true,
-                            enumerable: true
-                        });
-                    })(key);
+                    utils.defineGetterSetter(newNavigator,key,function() {
+                        return origNavigator[k];
+                    });
+                })(key);
             }
         }
     }
     return newNavigator;
 }
+
 if (window.navigator) {
     window.navigator = replaceNavigator(window.navigator);
 }
@@ -1293,6 +1292,7 @@ define("cordova/init_b", function(require, exports, module) {
 var channel = require('cordova/channel');
 var cordova = require('cordova');
 var platform = require('cordova/platform');
+var utils = require('cordova/utils');
 
 var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
 
@@ -1327,16 +1327,13 @@ function replaceNavigator(origNavigator) {
         for (var key in origNavigator) {
             if (typeof origNavigator[key] == 'function') {
                 newNavigator[key] = origNavigator[key].bind(origNavigator);
-            } else {
+            }
+            else {
                 (function(k) {
-                        Object.defineProperty(newNavigator, k, {
-                            get: function() {
-                                return origNavigator[k];
-                            },
-                            configurable: true,
-                            enumerable: true
-                        });
-                    })(key);
+                    utils.defineGetterSetter(newNavigator,key,function() {
+                        return origNavigator[k];
+                    });
+                })(key);
             }
         }
     }
@@ -1385,7 +1382,7 @@ platform.bootstrap && platform.bootstrap();
  * Create all cordova objects once native side is ready.
  */
 channel.join(function() {
-    
+
     platform.initialize && platform.initialize();
 
     // Fire event to notify that all objects are created
@@ -1546,11 +1543,27 @@ module.exports = {
         // Let native code know we are all done on the JS side.
         // Native code will then un-hide the WebView.
         channel.onCordovaReady.subscribe(function() {
+            exec(onMessageFromNative, null, 'App', 'messageChannel', []);
             exec(null, null, "App", "show", []);
         });
     }
 };
 
+function onMessageFromNative(msg) {
+    var cordova = require('cordova');
+    var action = msg.action;
+
+    switch (action)
+    {
+        case 'hidekeyboard':
+        case 'showkeyboard':
+            cordova.fireDocumentEvent(action);
+            break;
+        default:
+            throw new Error('Unknown event action ' + msg.action);
+    }
+}
+
 });
 
 // file: src/android/plugin/android/app.js

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/App.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java
index 9e8c1fe..6affb3d 100755
--- a/framework/src/org/apache/cordova/App.java
+++ b/framework/src/org/apache/cordova/App.java
@@ -41,8 +41,19 @@ import java.util.HashMap;
  */
 public class App extends CordovaPlugin {
 
+    public static final String PLUGIN_NAME = "App";
     protected static final String TAG = "CordovaApp";
     private BroadcastReceiver telephonyReceiver;
+    private CallbackContext messageChannel;
+
+    /**
+     * Send an event to be fired on the Javascript side.
+     *
+     * @param action The name of the event to be fired
+     */
+    public void fireJavascriptEvent(String action) {
+        sendEventMessage(action);
+    }
 
     /**
      * Sets the context of the Command. This can then be used to do things like
@@ -100,6 +111,11 @@ public class App extends CordovaPlugin {
             else if (action.equals("exitApp")) {
                 this.exitApp();
             }
+			else if (action.equals("messageChannel")) {
+                messageChannel = callbackContext;
+                return true;
+            }
+
             callbackContext.sendPluginResult(new PluginResult(status, result));
             return true;
         } catch (JSONException e) {
@@ -249,7 +265,7 @@ public class App extends CordovaPlugin {
     public void exitApp() {
         this.webView.postMessage("exit", null);
     }
-    
+
 
     /**
      * Listen for telephony events: RINGING, OFFHOOK and IDLE
@@ -290,6 +306,18 @@ public class App extends CordovaPlugin {
         webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
     }
 
+    private void sendEventMessage(String action) {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("action", action);
+        } catch (JSONException e) {
+            LOG.e(TAG, "Failed to create event message", e);
+        }
+        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
+        pluginResult.setKeepCallback(true);
+        messageChannel.sendPluginResult(pluginResult);
+    }
+
     /*
      * Unregister the receiver
      *

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/CordovaWebView.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java
index feec5a9..7c10e76 100755
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -155,7 +155,7 @@ public class CordovaWebView extends WebView {
         bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
         resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
 
-        pluginManager.addService("App", "org.apache.cordova.App");
+        pluginManager.addService(App.PLUGIN_NAME, "org.apache.cordova.App");
         // This will be removed in 4.0.x in favour of the plugin not being bundled.
         pluginManager.addService(new PluginEntry("SplashScreenInternal", "org.apache.cordova.SplashScreenInternal", true));
         pluginManager.init();

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/34397466/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
index bb6b6a8..6a8a2e3 100755
--- a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
+++ b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
@@ -18,10 +18,7 @@
 */
 package org.apache.cordova;
 
-import org.apache.cordova.LOG;
-
 import android.content.Context;
-//import android.view.View.MeasureSpec;
 import android.widget.LinearLayout;
 
 /**
@@ -36,6 +33,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
     private int screenWidth = 0;
     private int screenHeight = 0;
     private CordovaActivity app = null;
+    private App appPlugin = null;
 
     public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
         super(context);
@@ -50,7 +48,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
      * gets smaller fire a show keyboard event and when height gets bigger fire
      * a hide keyboard event.
      *
-     * Note: We are using app.postMessage so that this is more compatible with the API
+     * Note: We are using the core App plugin to send events over the bridge to Javascript
      *
      * @param widthMeasureSpec
      * @param heightMeasureSpec
@@ -87,14 +85,12 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
         // If the height as gotten bigger then we will assume the soft keyboard has
         // gone away.
         else if (height > oldHeight) {
-            if (app != null)
-                app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
+            sendEvent("hidekeyboard");
         }
-        // If the height as gotten smaller then we will assume the soft keyboard has 
+        // If the height as gotten smaller then we will assume the soft keyboard has
         // been displayed.
         else if (height < oldHeight) {
-            if (app != null)
-                app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
+            sendEvent("showkeyboard");
         }
 
         // Update the old height for the next event
@@ -102,4 +98,15 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
         oldWidth = width;
     }
 
+    private void sendEvent(String event) {
+        if (appPlugin == null) {
+            appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME);
+        }
+
+        if (appPlugin == null) {
+            LOG.w(TAG, "Unable to fire event without existing plugin");
+            return;
+        }
+        appPlugin.fireJavascriptEvent(event);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org