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:31 UTC
[4/4] android commit: Merge branch 'master' into 4.0.x (keyboard
events via PluginResult)
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