diff --git a/.travis.yml b/.travis.yml index e9919cdf..1e376701 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: android +sudo: false install: npm install script: - npm test diff --git a/framework/src/org/apache/cordova/AndroidChromeClient.java b/framework/src/org/apache/cordova/AndroidChromeClient.java index ad6c4143..7296ceb3 100755 --- a/framework/src/org/apache/cordova/AndroidChromeClient.java +++ b/framework/src/org/apache/cordova/AndroidChromeClient.java @@ -22,10 +22,14 @@ import org.apache.cordova.CordovaInterface; import org.apache.cordova.LOG; import android.annotation.TargetApi; +import android.app.Activity; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.os.Build; +import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; @@ -68,9 +72,6 @@ public class AndroidChromeClient extends WebChromeClient { //Keep track of last AlertDialog showed private AlertDialog lastHandledDialog; - // File Chooser - protected ValueCallback mUploadMessage; - public AndroidChromeClient(CordovaInterface ctx, AndroidWebView app) { this.cordova = ctx; this.appView = app; @@ -299,7 +300,10 @@ public class AndroidChromeClient extends WebChromeClient { } return mVideoProgressView; } - + + // support: + // openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat). + // For Lollipop, we use onShowFileChooser(). public void openFileChooser(ValueCallback uploadMsg) { this.openFileChooser(uploadMsg, "*/*"); } @@ -308,14 +312,39 @@ public class AndroidChromeClient extends WebChromeClient { this.openFileChooser(uploadMsg, acceptType, null); } - public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) + public void openFileChooser(final ValueCallback uploadMsg, String acceptType, String capture) { - mUploadMessage = uploadMsg; - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); - this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"), - FILECHOOSER_RESULTCODE); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + cordova.startActivityForResult(new CordovaPlugin() { + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); + Log.d(LOG_TAG, "Receive file chooser URL: " + result); + uploadMsg.onReceiveValue(result); + } + }, intent, FILECHOOSER_RESULTCODE); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser(WebView webView, final ValueCallback filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) { + Intent intent = fileChooserParams.createIntent(); + try { + cordova.startActivityForResult(new CordovaPlugin() { + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent); + Log.d(LOG_TAG, "Receive file chooser URL: " + result); + filePathsCallback.onReceiveValue(result); + } + }, intent, FILECHOOSER_RESULTCODE); + } catch (ActivityNotFoundException e) { + Log.w("No activity found to handle file chooser intent.", e); + filePathsCallback.onReceiveValue(null); + } + return true; } public void destroyLastDialog(){ diff --git a/framework/src/org/apache/cordova/AndroidWebView.java b/framework/src/org/apache/cordova/AndroidWebView.java index 109ad818..632e94a6 100755 --- a/framework/src/org/apache/cordova/AndroidWebView.java +++ b/framework/src/org/apache/cordova/AndroidWebView.java @@ -798,14 +798,6 @@ public class AndroidWebView extends WebView implements CordovaWebView { 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); diff --git a/framework/src/org/apache/cordova/AndroidWebViewClient.java b/framework/src/org/apache/cordova/AndroidWebViewClient.java index c0171774..0cad97cc 100755 --- a/framework/src/org/apache/cordova/AndroidWebViewClient.java +++ b/framework/src/org/apache/cordova/AndroidWebViewClient.java @@ -86,15 +86,22 @@ public class AndroidWebViewClient extends WebViewClient { @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { - // Get the authentication token + // Get the authentication token (if specified) AuthenticationToken token = this.getAuthenticationToken(host, realm); if (token != null) { handler.proceed(token.getUserName(), token.getPassword()); + return; } - else { - // Handle 401 like we'd normally do! - super.onReceivedHttpAuthRequest(view, handler, host, realm); + + // Check if there is some plugin which can resolve this auth challenge + PluginManager pluginManager = this.appView.pluginManager; + if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(this.appView, new CordovaHttpAuthHandler(handler), host, realm)) { + this.appView.loadUrlTimeout++; + return; } + + // By default handle 401 like we'd normally do! + super.onReceivedHttpAuthRequest(view, handler, host, realm); } /** diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index 8dec8057..30370c22 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -36,7 +36,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.media.AudioManager; -import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Display; @@ -95,7 +94,8 @@ public class CordovaActivity extends Activity implements CordovaInterface { private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down // Plugin to call when activity result is received - protected CordovaPlugin activityResultCallback = null; + protected int activityResultRequestCode; + protected CordovaPlugin activityResultCallback; protected boolean activityResultKeepRunning; /* @@ -381,7 +381,7 @@ public class CordovaActivity extends Activity implements CordovaInterface { * @param requestCode The request code that is passed to callback to identify the activity */ public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) { - this.activityResultCallback = command; + setActivityResultCallback(command); this.activityResultKeepRunning = this.keepRunning; // If multitasking turned on, then disable it for activities that return results @@ -389,8 +389,19 @@ public class CordovaActivity extends Activity implements CordovaInterface { this.keepRunning = false; } - // Start activity - super.startActivityForResult(intent, requestCode); + try { + startActivityForResult(intent, requestCode); + } catch (RuntimeException e) { // E.g.: ActivityNotFoundException + activityResultCallback = null; + throw e; + } + } + + @Override + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { + // Capture requestCode here so that it is captured in the setActivityResultCallback() case. + activityResultRequestCode = requestCode; + super.startActivityForResult(intent, requestCode, options); } /** @@ -400,32 +411,34 @@ public class CordovaActivity extends Activity implements CordovaInterface { * @param requestCode The request code originally supplied to startActivityForResult(), * allowing you to identify who this result came from. * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - LOG.d(TAG, "Incoming Result"); + LOG.d(TAG, "Incoming Result. Request code = " + requestCode); super.onActivityResult(requestCode, resultCode, intent); - Log.d(TAG, "Request code = " + requestCode); - if (appView != null && requestCode == AndroidChromeClient.FILECHOOSER_RESULTCODE) { - Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); - appView.onFilePickerResult(result); - } CordovaPlugin callback = this.activityResultCallback; if(callback == null && initCallbackClass != null) { // The application was restarted, but had defined an initial callback // before being shut down. - //this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass); - this.activityResultCallback = appView.getPluginManager().getPlugin(initCallbackClass); - callback = this.activityResultCallback; + callback = appView.getPluginManager().getPlugin(initCallbackClass); } - if(callback != null) { + initCallbackClass = null; + activityResultCallback = null; + + if (callback != null) { LOG.d(TAG, "We have a callback to send this result to"); callback.onActivityResult(requestCode, resultCode, intent); + } else { + LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it."); } } public void setActivityResultCallback(CordovaPlugin plugin) { + // Cancel any previously pending activity. + if (activityResultCallback != null) { + activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null); + } this.activityResultCallback = plugin; } diff --git a/framework/src/org/apache/cordova/CordovaHttpAuthHandler.java b/framework/src/org/apache/cordova/CordovaHttpAuthHandler.java new file mode 100644 index 00000000..724381e2 --- /dev/null +++ b/framework/src/org/apache/cordova/CordovaHttpAuthHandler.java @@ -0,0 +1,51 @@ +/* + 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 android.webkit.HttpAuthHandler; + +/** + * Specifies interface for HTTP auth handler object which is used to handle auth requests and + * specifying user credentials. + */ +public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler { + + private final HttpAuthHandler handler; + + public CordovaHttpAuthHandler(HttpAuthHandler handler) { + this.handler = handler; + } + + /** + * Instructs the WebView to cancel the authentication request. + */ + public void cancel () { + this.handler.cancel(); + } + + /** + * Instructs the WebView to proceed with the authentication with the given credentials. + * + * @param username + * @param password + */ + public void proceed (String username, String password) { + this.handler.proceed(username, password); + } +} diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java b/framework/src/org/apache/cordova/CordovaPlugin.java index f467bdfe..b243b381 100644 --- a/framework/src/org/apache/cordova/CordovaPlugin.java +++ b/framework/src/org/apache/cordova/CordovaPlugin.java @@ -196,4 +196,20 @@ public class CordovaPlugin { */ public void onReset() { } + + /** + * Called when the system received an HTTP authentication request. Plugin can use + * the supplied HttpAuthHandler to process this auth challenge. + * + * @param view The WebView that is initiating the callback + * @param handler The HttpAuthHandler used to set the WebView's response + * @param host The host requiring authentication + * @param realm The realm for which authentication is required + * + * @return Returns True if plugin will resolve this auth challenge, otherwise False + * + */ + public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) { + return false; + } } diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index 47abfc0d..8fd259c7 100644 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -84,8 +84,6 @@ public interface CordovaWebView { Whitelist getWhitelist(); Whitelist getExternalWhitelist(); CordovaPreferences getPreferences(); - - void onFilePickerResult(Uri uri); void setNetworkAvailable(boolean online); diff --git a/framework/src/org/apache/cordova/ICordovaHttpAuthHandler.java b/framework/src/org/apache/cordova/ICordovaHttpAuthHandler.java new file mode 100644 index 00000000..c55818ac --- /dev/null +++ b/framework/src/org/apache/cordova/ICordovaHttpAuthHandler.java @@ -0,0 +1,38 @@ +/* + 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; + +/** + * Specifies interface for HTTP auth handler object which is used to handle auth requests and + * specifying user credentials. + */ + public interface ICordovaHttpAuthHandler { + /** + * Instructs the WebView to cancel the authentication request. + */ + public void cancel (); + + /** + * Instructs the WebView to proceed with the authentication with the given credentials. + * + * @param username The user name + * @param password The password + */ + public void proceed (String username, String password); +} \ No newline at end of file diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java index 0c0b9c65..d48f8545 100755 --- a/framework/src/org/apache/cordova/PluginManager.java +++ b/framework/src/org/apache/cordova/PluginManager.java @@ -209,6 +209,27 @@ public class PluginManager { } } + /** + * Called when the system received an HTTP authentication request. Plugins can use + * the supplied HttpAuthHandler to process this auth challenge. + * + * @param view The WebView that is initiating the callback + * @param handler The HttpAuthHandler used to set the WebView's response + * @param host The host requiring authentication + * @param realm The realm for which authentication is required + * + * @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False + * + */ + public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) { + for (CordovaPlugin plugin : this.pluginMap.values()) { + if (plugin != null && plugin.onReceivedHttpAuthRequest(view, handler, host, realm)) { + return true; + } + } + return false; + } + /** * Called when the activity will start interacting with the user. *