JavaFX WebView: can't get JS bridge to work in Java11+ - javascript

I can't get the Java / Javascript bridge to work on Java11 and Java13. The bridge seems to work fine i Java8 and Java10.
Here is essentially the same code as https://stackoverflow.com/a/34840552/11329518, which again works for me on Java8 and Java10:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javafx.application.Application;
import javafx.concurrent.Worker.State;
import javafx.scene.control.ButtonType;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
JavaBridge bridge;
WebEngine webEngine;
#Override
public void start(Stage primaryStage) throws MalformedURLException {
final URL url = new File("C:/test.html").toURI().toURL();
WebView webView = new javafx.scene.web.WebView();
webEngine = webView.getEngine();
webEngine.load(url.toExternalForm());
webEngine.setJavaScriptEnabled(true);
webEngine.setOnAlert(Main::showAlert);
webEngine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
System.out.println("READY");
JSObject jsobj = (JSObject) webEngine.executeScript("window");
bridge = new JavaBridge();
jsobj.setMember("bridge", bridge);
}
});
primaryStage.setScene(new javafx.scene.Scene(webView, 300, 300));
primaryStage.show();
}
// Shows the alert, used in JS catch statement
private static void showAlert(WebEvent<String> event) {
javafx.scene.control.Dialog<ButtonType> alert = new javafx.scene.control.Dialog<>();
alert.getDialogPane().setContentText(event.getData());
alert.getDialogPane().getButtonTypes().add(ButtonType.OK);
alert.showAndWait();
}
public class JavaBridge {
public void hello() {
System.out.println("hello");
}
}
}
with test.html containing the Javascript:
<button onclick="try{bridge.hello();}catch(err){alert(err.message);}">call java</button>
What's going on?
I get the following error when clicking the button:
bridge.hello is not a function. (In 'bridge.hello()', 'bridge.hello' is undefined)

I can't reproduce your issue with JavaFX 13 or 14-ea+6, using Java 11 (OpenJDK 11.0.2) or Java 13 (OpenJDK 13).
However I can reproduce the issue, if I remove the strong reference to JavaBridge and I use Java 11.
This:
jsobj.setMember("bridge", new JavaBridge());
fails with the same error you have posted, with Java 11. But when using Java 13 (OpenJDK 13), that works fine (and also with Java 12).
Are you using other Java vendors? Can you try with OpenJDK https://jdk.java.net/13/?

There are two things that resolved this issue with OpenJDK 14 and JavaFx 14
Hard Reference to Bridge Object
bridge = new JavaBridge(); // create Bridge before hand
webEngine.getLoadWorker().stateProperty().addListener((ov, oldState,
newState) -> {
if (newState == State.SUCCEEDED) {
System.out.println("READY");
JSObject jsobj = (JSObject) webEngine.executeScript("window");
jsobj.setMember("bridge", bridge);
}
});
Use Bridge in Javascript/HTML page after the page is loaded
window.onload = function() {
bridge.hello();
}

Related

Empty page using sendIntent.putExtras(bundle), page not filled Android studio

I'm trying to send some stuff via mail or Bluetooth etc but not working very well.
I would like to see a text like this:
" Respiration Rate: 0 2.0 5.0 16.0 "...
To do that I've implemented a button and Resut.java stuff. When I try to click on the button and I choose e-mail, app open a mail and the text is only "Respiration Rate"
here click-button:
SRR.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
Bundle bundle = new Bundle();
bundle.putDoubleArray("TryThis",plot_array);
sendIntent.putExtras(bundle);
sendIntent.putExtra(Intent.EXTRA_TEXT, "Respiration Rate: " );
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
}
});
result.java:
package com.google.android.gms.samples.vision.face.rPPG;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
import com.jjoe64.graphview.series.PointsGraphSeries;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
public class RespirationResult extends AppCompatActivity {
private String Date;
int RR;
int il_risultato;
double [] plot_array;
int[] intArray;
DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
java.util.Date today = Calendar.getInstance().getTime();
private String[] RRtoSent=new String[300];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_respiration_result);
Date = df.format(today);
TextView RRR = (TextView) this.findViewById(R.id.RRR);
ImageButton SRR = (ImageButton)this.findViewById(R.id.SendRR);
//-------------------------------------------------------------------------risutato
Bundle b = getIntent().getExtras();
double result = b.getDouble("key");
plot_array=b.getDoubleArray("array");
il_risultato=(int) Math.round(result);
RRR.setText(String.valueOf(il_risultato)); //prima era RR, da mettere successivamente
RRtoSent = new String[plot_array.length];
for (int i = 0; i < RRtoSent.length; i++) {
RRtoSent[i] = String.valueOf(plot_array[i]);
}
//grafico
GraphView graph;
LineGraphSeries<DataPoint> series; //an Object of the PointsGraphSeries for plotting scatter graphs
graph = (GraphView) findViewById(R.id.graphico);
series= new LineGraphSeries<>(data()); //initializing/defining series to get the data from the method 'data()'
graph.addSeries(series); //adding the series to the GraphView
//series.setShape(PointsGraphSeries.Shape.POINT);
// activate horizontal and vertical zooming and scrolling
graph.getViewport().setScalableY(true);
graph.getGridLabelRenderer().setGridColor(Color.DKGRAY);
graph.getGridLabelRenderer().setHorizontalLabelsColor(Color.DKGRAY);
graph.getGridLabelRenderer().setVerticalLabelsColor(Color.DKGRAY);
// graph.setTitle("Respiration Rate/min");
graph.getGridLabelRenderer().setHorizontalAxisTitle("time(sec)");
graph.getGridLabelRenderer().setVerticalAxisTitle("RR");
// set manual X bounds
graph.getViewport().setXAxisBoundsManual(true);
graph.getViewport().setMinX(0.5);
graph.getViewport().setMaxX(100);
SRR.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
Bundle bundle = new Bundle();
bundle.putDoubleArray("TryThis",plot_array);
sendIntent.putExtras(bundle);
sendIntent.putExtra(Intent.EXTRA_TEXT, "Respiration Rate: " );
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
}
});
}
public DataPoint[] data(){
DataPoint[] values = new DataPoint[plot_array.length]; //creating an object of type DataPoint[] of size 'n'
for(int i=0;i<plot_array.length;i++){
DataPoint v = new DataPoint(i,plot_array[i]);
values[i] = v;
}
return values;
}
#Override
public void onBackPressed() {
Intent i = new Intent(RespirationResult.this, SplashScreen.class);
startActivity(i);
finish();
super.onBackPressed();
}
}
Does anyone have an idea about it?
what's wrong or missing?
Thanks in advance
From Android official site:
public static final String EXTRA_TEXT
A constant CharSequence that is associated with the Intent, used with
ACTION_SEND to supply the literal data to be sent. Note that this may
be a styled CharSequence, so you must use Bundle.getCharSequence() to
retrieve it.
So you should convert your double array to String first before calling email app.
Step 1: Write a method which convert a double array to a string.
private String convertDoubleArrayToString(double[] array) {
StringBuilder sb = new StringBuilder();
for (double number: array) {
sb.append(number).append(" ");
}
return sb.toString();
}
Step 2: Change your code to
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "Respiration Rate: " + convertDoubleArrayToString(plot_array));
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
That's because all you want to show must be part of the EXTRA_TEXT:
sendIntent.putExtra(Intent.EXTRA_TEXT, "EVERYTHING YOU WANT TO PRINT HERE" );
So you have to build a string with your data, then you create the extra with the complete string.

Calling JavaFx from javaScript

Is it possible to call JavaFx application from javaScript? Now i'm able to call an html file from javaFX using
webview = new WebView();
webview.setVisible(true);
webengine = webview.getEngine();
webengine.setJavaScriptEnabled(true);
File file = new File("C:\\sample.html");
System.out.println(file.exists() + " file exitence");
webengine.load(file.toURI().toURL().toString());
if possible please help me. i'm new to java and JavaFx.
In JS: alert("HEAD;DATA");
In Java:
WebView webView = new WebView();
final WebEngine engine = webView.getEngine();
engine.setOnAlert(new EventHandler<WebEvent<String>>() {
public void handle(WebEvent<String> e) {
String[] infs = e.getData().split(";");
if(infs[0].equals("HEAD")){
System.out.println(infs[1]);
}
}
});

Cordova / Android: Use java method in Javascript to list file in resources

I want to list all the files in a specific folder in my a Javascript that I use to manipulate my HTMLs.
I'm using a java method to list all the files that I to display.
Here's the the class.
public class JavaScriptInterface {
private Context context;
public JavaScriptInterface(Context current){
this.context = current;
}
#JavascriptInterface
public List<String> getFileNames(String path){
String [] files;
try {
files = context.getAssets().list(path);
ArrayList<String> testName = new ArrayList<String>();
for (String file: files) {
Log.d("File name: ", file);
file = file.replace(".js", "");
String[] fileName = file.split("_");
testName.add(fileName[1]);
}
return testName;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
Then I added this in my mainActivity
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.init();
setToUnstrictMode();
WebView wV = (WebView)appView.getEngine().getView();
wV.addJavascriptInterface(new JavaScriptInterface(this), "jsInterface");
// Set by <content src="index.html" /> in config.xml
wV.loadUrl(launchUrl);
}
Then I tried accessing it in my JS using this function.
var getTestNames = function(){
return window.jsInterface.getFileNames("www/js/tests");
}
Apparently, It's not working.
EDIT: I noticed that I was able to call the method, the log from the method getFileNames shows up in my logs. However, for some reasons, I could not pass it properly in a variable.
Maybe just write a simple plugin. I wrote some by myself i can approve that they are working. Use tips from official cordova documentation: https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html
I know that it's different approach, but at least it's a working one.

How to pass a camera image to mail as an attachment. (Android, iOS, Windows Phone, Phonegap)

I am developing an app for my work (social housing) and I want it to be able to allow the user to take a photo and attach it to an email so they can send it to us (pictures of repairs etc)
I'm using Phonegap and Eclipse as I want the app to be cross platform but am testing in Android primarily at the moment. Is there a way to do this? I am currently using the code below with no avail.
<script typr="text/javascript" charset="utf-8">
function camera()
{
navigator.camera.getPicture(onSuccess, onFail, { quality: 20,
destinationType: Camera.DestinationType.DATA_URL
});
function onSuccess(imageData) {
var image = document.getElementById('image');
var data = "data:image/jpeg;base64," + imageData;
var link = "mailto:johnsmith#gmail.com?body="+data+"&subject=john smith";
window.location.href = link;
}
function onFail(message) {
alert('Failed because: ' + message);
}
}
</script>
So far I've tried passing the data through to the mail app using the mailto: &attachment method but that never attaches an image (most mail apps treat this as a security hole). Then I tried to embed the base64 code of the image in the body of the email (as shown above). Unfortunately the base64 just displays as plain text and makes mail unresponsive. I've also tried using the image URI instead of the Base64 method in Phonegap but that throws an 'image.URI is not defined' error in my logcat.
Is this possible? I know I can use intents for just android as detailed in another question here but this won't work on iOS etc.
Any help would be greatly appreciated.
EDIT 02/12/2012
What I'm trying to achieve here is the same functionality you get in the native Android gallery/camera app. After you take a picture you have share options, one of which is mail. If you chose to share via mail the image is passed to the mail app as an attachment. Is there any way I can implement this same functionality in my app?
So it looks like there is no 'one size fits all' solution for this problem.
The mailto: method just doesn't pass attachments in most modern mail apps as it's seen as a security risk. So regardless of whether its an imageURI or a base64 encoded image, mailto: just won't work. The passing of 'subject' and 'body' works well though for anyone that wants to use the above code for pre-filling in an email with no attachments.
After posing this question elsewhere it looks like I'll need to use a phonegap plugin (emailComposer for iOS and WebIntent for Android) in order to pass an image sucessfully to a mail app from my phonegap app.
Thanks.
use this JAVA code to send Email with photo and text.
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Security;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
public class GMailSender extends javax.mail.Authenticator {
private String mailhost = "smtp.gmail.com";
private String user;
private String password;
private Session session;
static {
Security.addProvider(new JSSEProvider());
}
public GMailSender(String user, String password) {
this.user = user;
this.password = password;
Properties props = new Properties();
props.setProperty("mail.transport.protocol", "smtp");
props.setProperty("mail.host", mailhost);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
props.setProperty("mail.smtp.quitwait", "false");
session = Session.getDefaultInstance(props, this);
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password);
}
public synchronized void sendMail(String subject, String body, String sender, String recipients) throws Exception {
try {
MimeMessage message = new MimeMessage(session);
DataHandler handler = new DataHandler(new ByteArrayDataSource(body.getBytes(), "text/plain"));
message.setSender(new InternetAddress(sender));
message.setSubject(subject);
message.setDataHandler(handler);
if (recipients.indexOf(',') > 0)
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipients));
else
message.setRecipient(Message.RecipientType.TO, new InternetAddress(recipients));
Transport.send(message);
} catch (Exception e) {
}
}
public synchronized void sendMail(String subject, String body, String senderEmail, String recipients, String filePath,String logFilePath) throws Exception {
boolean fileExists = new File(filePath).exists();
if (fileExists) {
String from = senderEmail;
String to = recipients;
String fileAttachment = filePath;
// Define message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject);
// create the message part
MimeBodyPart messageBodyPart = new MimeBodyPart();
// fill message
messageBodyPart.setText(body);
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Part two is attachment
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(fileAttachment);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName("screenShoot.jpg");
multipart.addBodyPart(messageBodyPart);
//part three for logs
messageBodyPart = new MimeBodyPart();
DataSource sourceb = new FileDataSource(logFilePath);
messageBodyPart.setDataHandler(new DataHandler(sourceb));
messageBodyPart.setFileName("logs.txt");
multipart.addBodyPart(messageBodyPart);
// Put parts in message
message.setContent(multipart);
// Send the message
Transport.send(message);
}else{
sendMail( subject, body, senderEmail, recipients);
}
}
public class ByteArrayDataSource implements DataSource {
private byte[] data;
private String type;
public ByteArrayDataSource(byte[] data, String type) {
super();
this.data = data;
this.type = type;
}
public ByteArrayDataSource(byte[] data) {
super();
this.data = data;
}
public void setType(String type) {
this.type = type;
}
public String getContentType() {
if (type == null)
return "application/octet-stream";
else
return type;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(data);
}
public String getName() {
return "ByteArrayDataSource";
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Not Supported");
}
}
}

How to call phone plugin in javascript

Ok I am trying to implement an phone gap plugin, that consists of two parts. I am using cordova 2.0.0 and eclipse.
Here is the java part:
package org.apache.cordova;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import android.graphics.Bitmap;
import android.os.Environment;
import android.view.View;
public class Screenshot extends Plugin {
#Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
// starting on ICS, some WebView methods
// can only be called on UI threads
final Plugin that = this;
final String id = callbackId;
super.cordova.getActivity().runOnUiThread(new Runnable() {
//#Override
public void run() {
View view = webView.getRootView();
view.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
try {
File folder = new File(Environment.getExternalStorageDirectory(), "Pictures");
if (!folder.exists()) {
folder.mkdirs();
}
File f = new File(folder, "screenshot_" + System.currentTimeMillis() + ".png");
FileOutputStream fos = new FileOutputStream(f);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
that.success(new PluginResult(PluginResult.Status.OK), id);
} catch (IOException e) {
that.success(new PluginResult(PluginResult.Status.IO_EXCEPTION, e.getMessage()), id);
}
}
});
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
result.setKeepCallback(true);
return result;
}
}
Here is the javascript part:
cordova.define("cordova/plugin/screenshot", function(require, exports, module) {
var exec = require('cordova/exec');
/**
* This class exposes the ability to take a Screenshot to JavaScript
*/
var Screenshot = function() {};
/**
* Save the screenshot to the user's Photo Library
*/
Screenshot.prototype.saveScreenshot = function() {
exec(null, null, "Screenshot", "saveScreenshot", []);
};
var screenshot = new Screenshot();
module.exports = screenshot;
});
if (!window.plugins) {
window.plugins = {};
}
if (!window.plugins.screenshot) {
window.plugins.screenshot = cordova.require("cordova/plugin/screenshot");
}
I am trying to call this with another javascript function on another page, but without success. I hide the anchors of an image on a canvas, then this line:
setTimeout(takeScreenShot,500);
EDIT -- made after Simon MacDonald's answer
this then relates to a javascript function:
function takeScreenShot() {
window.plugins.screenshot.saveScreenshot();
}
The following java prints:
System.out.println(folder);
System.out.println("screenshot_" + System.currentTimeMillis() + ".png");
Produce the following results:
/mdt/sdcard/Pictures
screenshot_1347893081276.png
EDIT After turning the device off and on again, the screenshots I took appeared, the phone seems to cache them, and not actually store them to the selected folder.
I have ensured that my config.xml and my android manifest have the right permissions and lines of code. Anyone see where im going wrong?
nowhere in your code do you ever call the saveScreenshot method. Your takeScreenShot method should look like this:
function takeScreenShot() {
window.plugins.screenshot.saveScreenshot();
}
Then the screen shot should be saved in "/sdcard/Pictures". This will work if:
You remembered to put a plugin line into your config.xml for the screen shot plugin
You mentioned this is on another page so make sure that page has a script tag for screenshot.js.
Did you try with the Obsolute Path?
File sdCard = Environment.getExternalStorageDirectory();
File folder = new File (sdcard.getAbsolutePath() + "Pictures");

Categories

Resources