Crosswalk Cordova Android multiple file select - javascript

I have a hybrid app built using cordova and angularjs, for Android I run the app using crosswalk.
I've been scouring the internet to find the solution for the html5 file input to allow selection of multiple files.
I'm using the following element for file selecting:
<input type="file" multiple="multiple" name="files[]" />
I am running Android Lollipop version 5.1.1 and Crosswalk version 20, I have tested with Crosswalk version 18 and 19 also. Chrome is installed on my device running the latest version although I don't think that makes a difference.
When I click the input element above I get the expected dialog asking me to select from my Documents or Camera. If I choose to select from my Documents then I am only able to select single files, in this case images. This is true for every App that I can select images from, so the default android 'Images', 'Videos', 'Audio', etc and external Apps such as Google Photos - All only allow me to select one single file at a time.
In the image below you can see the files listed, a long press on each tile does not add the file to a multiple selection.
This works on the IOS version of the App.
After digging through all the material I can find online it seems that the multiple attribute is supported on Android 5+ running Chrome 49+.
I'm unsure if this is a crosswalk browser implementation or Android Operating System issue, or something else? Could anyone advise.
Edit
Just to confirm this does not work with or without using Crosswalk.

After weeks of trying to sort this out, I finally got it to work (Cordova without Crosswalk). This was done using Cordova Tools in Windows so please pardon the filespecs below.
Step 1: Change the minSdkVersion in platforms\Android\CordovaLib\AndroidManifest.xml to 21
Explanation: onShowFileChooser API was introduced in LOLLIPOP (API 21). It allows returning url[] instead of url returned by showFileChooser in earlier API versions. This gets called only when you change the API to 21 or greater.
Step 2: Update/Replace the onActivityResult method to retrieve multiple files.
Append the following after creating intent using fileChooserParams to allow choosing multiple files:
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
Location: platforms\android\CordovaLib\src\org\apache\cordova\engine\SystemWebChromeClient.java
Step 3: Update the corresponding onActivityResult method to return multiple urls using intent.getClipData().
Caveats:
Enables Multi-upload for all calls. You could update the intent based on fileChooserParams mode.
Disables Camera as a source in chooser which is available with crosswalk by default.
Final Code:
Uri photoUri;
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
// Check and use MIME Type.
String mimeType = "*/*";
int ACTION_CODE = FILECHOOSER_RESULTCODE;
try {
if (fileChooserParams.getAcceptTypes().length > 0) {
mimeType = fileChooserParams.getAcceptTypes()[0];
} else {
mimeType = "*/*";
}
} catch (Exception e) {
mimeType = "*/*";
};
// Check if Mutiple is specified
Boolean selectMultiple = false;
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
selectMultiple = true;
};
Intent intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
if (selectMultiple) { intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); };
intent.setType(mimeType);
ACTION_CODE = FILECHOOSER_RESULTCODE;
final Intent chooserIntent = Intent.createChooser(intent, "Select Source");
// Add camera intent to the chooser if image and send URI to return full image
if (mimeType.equals("image/*")) {
photoUri = null;
try {
File photoFile = createImageFile();
photoUri = Uri.fromFile(photoFile);
}
catch (Exception ex) {
photoUri = null;
}
if (photoUri != null) {
Intent camIntent = new Intent();
camIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
camIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
camIntent.putExtra("return-data", true);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent [] {camIntent} );
}
}
try {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK && intent != null) {
if (intent.getData() != null)
{
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
filePathsCallback.onReceiveValue(result);
}
else
{
if (intent.getClipData() != null) {
final int numSelectedFiles = intent.getClipData().getItemCount();
Uri[] result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
result[i] = intent.getClipData().getItemAt(i).getUri();
}
filePathsCallback.onReceiveValue(result);
}
else {
filePathsCallback.onReceiveValue(null);
}
}
}
else if(resultCode == Activity.RESULT_OK && (intent == null || intent.getData() == null )) {
Uri[] result = new Uri[1];
result[0] = photoUri;
filePathsCallback.onReceiveValue(result);
} else {
filePathsCallback.onReceiveValue(null);
}
}
}, chooserIntent, ACTION_CODE);
} catch (ActivityNotFoundException e) {
Log.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}

Related

How to prevent page reload after JavaScript function is executed?

I am developing a web application in java. But for certain purposes, I have to use javascript. I am facing an infinitive loop issue caused by I believe that very same javascript.
I trigger the init method in java bean by clicking on the command link. This command link should open a new XHTML page. During init, I have to resolve the local IP address which I need for further implementation. I found javascript here on Stack Over Flow which will resolve that for me. After executing javascript I need to store that IP into some variable and pass it back to bean. I found a way of doing it by using <p:remoteCommand> which I trigger right from javascript itself. I managed to pass the variable to bean successfully, and use it for other functionality. At this point, I'm facing a problem, which I have no clue how to solve. After the script is executed successfully somehow the page is reloaded, and the init method is invoked again. So it basically creates an infinite loop.
Here is my init method in java bean:
public void init() {
RequestContext.getCurrentInstance()
.execute("getLocalIP();");
}
JavaScript for resolving local IP address:
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var pc = new RTCPeerConnection({
iceServers : []
}), noop = function() {};
pc.createDataChannel('');
pc.createOffer(pc.setLocalDescription.bind(pc), noop);
pc.onicecandidate = function(ice) {
if (ice && ice.candidate && ice.candidate.candidate) {
var localIP = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4})
{7})/.exec(ice.candidate.candidate)[1];
console.log('my IP: ', localIP);
// this will trigger the remoteCommand
// which will pass value from JS to java bean
loadLocalIP([{name:'localIPParam', value: localIP}]);
pc.onicecandidate = noop;
}
}
Here is my xhtml(remoteCommand) which is triggered from javascript:
<h:form id="localIPForm">
<p:remoteCommand name="loadLocalIP" action="#{aggregationStationController.loadData()}"/>
</h:form>
This method is invoked by remoteCommand:
public void loadData() {
Map<String, String> params = FacesContext.getCurrentInstance()
.getExternalContext()
.getRequestParameterMap();
ipValue = params.get("localIPParam").toString();
System.out.println(ipValue);
if (!ipValue.isEmpty()) {
resolveAggregationStationByIP(ipValue);
}
// after this line init method is invoked again and creates an infinite loop
}
private void resolveAggregationStationByIP(String ipAddress) {
aggregationStation = organizationStructureService.getOrgEntityByTypesAndAttrCodeAndAttrValue(
Arrays.asList(OrgEntityTypeCode.AGGREGATION_STATION.getCode(),
OrgEntityTypeCode.PROD_LINE.getCode()), "IP", ipAddress);
if (aggregationStation != null) {
printerName = organizationStructureService.
getOrgEntityAttrValue(aggregationStation, "PRINTER_NAME");
if (printerName == null) {
printerName = "";
}
// check if there is order in progress for station
selectedPackagingOrder = getInProgressOrderForStation();
loadOperationTypes();
populateDataFromDB();
} else {
error = true;
errorMessage = MessageUtil.interpolate("ip_not_registred_as_station", ipAddress,
MessageUtil.interpolate(OrgEntityTypeCode.AGGREGATION_STATION.getCode()));
}
}
My question is: How to avoid javascript execution more than once?
I would appreciate any help I can get to sort this one out. Thanks in advance!
Try and use event.preventDefault() to prevent the page refreshing.

C# boolean function is returning an object from server to client

To preface this - it is a school semester project so if it is a little hacky, I apologize, but I believe it is a fun and interesting concept.
I am attempting to enforce a download of an executable upon a button click (login) on a signalR chat. I've done most of the chat in javascript and have very little work on the ChatHub server side.
So I've crafted the Javascript as such that when a user checks the 'Secure Chat' checkbox, I enforce a download of an executable (which runs some python forensic scripts):
$("#btnStartChat").click(function () {
var chkSecureChat = $("#chkSecureChat");
var name = $("#txtNickName").val();
var proceedLogin = false;
if (chkSecureChat.is(":checked")) {
proceedLogin = chatHub.server.secureLogin();
isSecureChat = true;
} else {
proceedLogin = true;
}
The chatHub.server.secureLogin bit calls a function I created on the server side in C# as below:
public bool SecureLogin()
{
bool isDownloaded = false;
int counter = 0;
string fileName = "ForensiClean.exe";
string userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string downloadPath = (userPath + "\\Downloads\\" + fileName);
// try three times
while(isDownloaded == false && counter < 3)
{
if (System.IO.File.Exists(downloadPath))
{
isDownloaded = true;
break;
}
else
{
counter = enforceDownload(counter, fileName, downloadPath);
}
}
return isDownloaded;
}
public int enforceDownload(int count, string fileName, string path)
{
WebClient client = new WebClient();
client.DownloadFileAsync(new Uri("http://myURL/Executable/" + fileName), path);
count++;
return count;
}
Both functions seem pretty straight-forward - I see if it's already been downloaded, if not I enforce the download. It works while in development. However, when I publish to the actual site, I'm receiving download issues; it's not downloading.
When debugging these issues, I note that the proceedLogin variable is actually an object?!?! (as shown in the image). Please help with any ideas, I'm stumped.
It looks like proceedLogin is a promise object.
Try this:
if (chkSecureChat.is(":checked")) {
chatHub.server.secureLogin().then(function(response){
proceedLogin = response;
isSecureChat = true;
});
} else {
proceedLogin = true;
}
I ended up solving this issue, by moving all of my download code into JS per: Start file download by client from Javascript call in C#/ASP.NET page? It is, after all, a school project - so I gotta get moving on it.
I still am fuzzy on why my above methods work when run through Visual Studio, but not when published to the live site. Thank you #Cerbrus and #SynerCoder for your responses.

Calling JavaScript function in Xamarin.Forms IOS WebViewRenderer

I am using Xamarin.Forms to build a cross platform IOS Android and windows phone app. One of my views is a web view which calls a url pointing to a page that has a javascript function in it. I need to call this function from within the mobile app, and pass it a string value.
So far all good, I achieve this in WindowsPhone by using a WebViewRenderer with a custom view. In the OnElementPropertyChanged handler in the renderer I can get get access to my view and the properties I need like so:
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(e.PropertyName == SessionWebView.SessionIdProperty.PropertyName)
{
view = Element as SessionWebView;
Control.LoadCompleted += Control_LoadCompleted;
if (view != null)
{
_currentSessionId = view.SessionId;
}
}
}
Above I attach the the load complete handler of the Control which is the actual BrowserControl. Then below I can call the desired JavaScript function in the webpage.
void Control_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (!scriptCalled)
{
Control.IsScriptEnabled = true;
var param = new string[1];
param[0] = _currentSessionId;
Control.InvokeScript("initWebView", param);
scriptCalled = true;
}
}
This all works perfectly in Windows Phone. In IOS - which is what this question is about - I do something similar with a custom Rendered, And I can get my view and its properties.
However - I am unable to get the Native browser control in IOS to be able to call a JavaScript function on it. This is what I have so far:
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (_view == null && e.NewElement is SessionWebView)
{
_view = (SessionWebView) e.NewElement;
// Attach to the PropertyChanged event on the view
_view.PropertyChanged += _view_PropertyChanged;
}
}
private void _view_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Look for the sessionId propery
if (e.PropertyName == SessionWebView.SessionIdProperty.PropertyName)
{
if(_view != null && !string.IsNullOrEmpty(_view.SessionId))
{
// if we have a view set the sessionId
_currentSessionId = _view.SessionId;
// Now need Browser to attach to loaded or navaigated events...
}
}
}
Any idea on how I could access the Browser control to call the JavaScript function?
Have you had a look on XLabs HybridWebView? https://github.com/XLabs/Xamarin-Forms-Labs/blob/master/src/Forms/XLabs.Forms.iOS/Controls/HybridWebView/HybridWebViewRenderer.cs
It's based on UIWebView control and uses UIWebView.EvaluateJavascript method. Which native browser control do you use? Could you reveal the full renderer code?

WinRT - Starting/Registering IBackgroundTask in universal application

I would like to start my IBackgroundTask when my application starts up.
I have added my task to the .appxmanifest.xml file, and my extensions tag now looks like this
<Extensions>
<Extension Category="windows.backgroundTasks" EntryPoint="CordovaApp.Library.UploadTask">
<BackgroundTasks>
<Task Type="systemEvent" />
<Task Type="timer" />
</BackgroundTasks>
</Extension>
</Extensions>
My IBackgroundTask class is called UploadTask and is held in another project which has the outtype set to Windows Runtime Component.
Here is a cut down version of the code, so you can see the namespace etc
namespace CordovaApp.Library
{
public sealed class UploadTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
var connectionProfile = NetworkInformation.GetInternetConnectionProfile();
// connectionProfile can be null (e.g. airplane mode)
if (connectionProfile != null && connectionProfile.IsWlanConnectionProfile)
{
// custom code here
}
}
}
}
I have added a reference to this project to my universal runtime component project.
Everything builds fine.
Now to start the application, i guess i have to use WinJs, so i have the following code.
var uploadTaskName = 'UploadTask';
var tasks = Windows.ApplicationModel.Background.BackgroundTaskRegistration.allTasks;
var uploadTaskFound = false;
for (var i = 0; i < tasks.length; i++) {
if (tasks[i].Value.name == uploadTaskName) {
successCallback();
return;
}
}
Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync().then(function() {
var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder();
builder.name = "Upload Task";
builder.taskEntryPoint = "CordovaApp.Library.UploadTask";
builder.setTrigger(new Windows.ApplicationModel.Background.TimeTrigger(15, false));
return builder.register();
}).done(function () {
successCallback();
}, function(err) {
errorCallback(err);
});
Now the requestAccessAsync method always throws an exception of
0x80004005 - JavaScript runtime error: Unspecified error
WinRT information: The application is not lock-screen capable.
Have a registered everything correctly? I am running this via Visual Studio 2013 on a laptop.
Seems that because the app was already installed, the permission was not given.
By uninstalling the application, and re-running it, i was then prompted to allow/disallow the background service to run. Checked allow, and now seems to work

MSScriptControl Issue on Windows Server 2008

So I'm using the MSScriptControl to run some javascript in my app and I want to be able to get some information about any errors the script may cause.
MSScriptControl.ScriptControlClass script = new MSScriptControl.ScriptControlClass();
try
{
script.Language = "JScript";
script.Timeout = 15000;
script.Eval(Code);
}
catch (Exception ex)
{
MSScriptControl.Error err = script.Error;
ret = new Exception("Error on line: " + err.Line + ", Description: " + err.Description);
}
The code works fine on my development machine, a Windows 7 box, and gives me a line number with an error. So I happily publish and push it to the production machine which always tells me the error occurred at line 0 and no description is provided.
I tried going to http://www.microsoft.com/download/en/details.aspx?id=1949 to download the latest version but installing it had no effect. I also set the property Embed Interop Types to false as well as copying my own msscript.ocx file into the Windows 2008 server's system32 directory but neither of these attempts resolved anything.
Anyone have any recommendations?
If you want to do it in all native c# without any 3rd party or "component" external dependencies use a CodeDomProvider with a tiny JScript bootstrap, like this:
private static readonly MethodInfo eval = CodeDomProvider
.CreateProvider("JScript")
.CompileAssemblyFromSource(new CompilerParameters(), "package e{class v{public static function e(e:String):Object{return eval(e);}}}")
.CompiledAssembly
.GetType("e.v")
.GetMethod("e");
private static object JsEval(string jscript)
{
try
{
return eval.Invoke(null, new[] { jscript });
}
catch (Exception ex)
{
return ex;
}
}
that creates a JsEval(string) method that you can use anywhere in your code to "eval" a string as JavaScript (well JScript)... So calling:
MessageBox.Show("" + JsEval("2 + 2")); // 4
MessageBox.Show("" + JsEval("(function(){ return 3+7; })();")); // 10
MessageBox.Show("" + JsEval("function yay(a) { return a + 1; } yay(2);")); // 3
depending on your use you may not want to instantiate these members statically. if you want to manipulate complex objects you will need create a wrapper to reflectively extract data (or you could cast as the appropriate JScript counterpart, but I've never tried this as you'd have to include the JScript assemblies).
here is an example of a wrapper class that does everything JavaScript will let you do natively, adding anymore high level functionality would probably be cumbersome enough so that you'd be better off either extracting the members into a dictionary / hash table OR alternatively serializing and deserializing on the other end
private class JsObjectWrapper : IEnumerable
{
public readonly object jsObject;
private static PropertyInfo itemAccessor = null;
private static MethodInfo getEnumerator = null;
public JsObjectWrapper(object jsObject)
{
this.jsObject = jsObject;
if (itemAccessor == null)
{
itemAccessor = jsObject.GetType().GetProperty("Item", new Type[] { typeof(string) });
}
if (getEnumerator == null)
{
getEnumerator = jsObject.GetType().GetInterface("IEnumerable").GetMethod("GetEnumerator");
}
}
public object this[string key]
{
get { return itemAccessor.GetValue(jsObject, new object[] { key }); }
set { itemAccessor.SetValue(jsObject, value, new object[] { key }); }
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)getEnumerator.Invoke(jsObject, null);
}
}
you can see this in action by doing this:
var jsObj = JsEval("var x = { a:7, b:9 };");
var csObj = new JsObjectWrapper(jsObj);
MessageBox.Show("a: " + csObj["a"]); // a: 7
MessageBox.Show("b: " + csObj["b"]); // b: 9
csObj["yay!"] = 69;
foreach (string key in csObj)
{
MessageBox.Show("" + key + ": " + csObj[key]); // "key": "value"
}
i personally have used code similar to this to great effect at one point or another and can vouch for it's availability and runnability inside a server environment.. I hope this helps -ck
Regarding the problem you face just some thoughts:
according to the link you provided this control neither supports Windows 7 nor Windows 2008
it might be a security issue with regards to COM/UAC etc.
it might be a problem because of bitness if you compiled for AnyCPU, try using x86
Regarding possible alternatives:
Using JScript you can build an evaluator rather easily which is supported anywhere .NET 4 runs (including Windows Server 2008).
Using JInt as a JavaScript interpreter

Categories

Resources