I've seen several questions on this but all of the solutions didn't work for me. For a client we have to develop an app that actually does nothing except of showing a WebView and a native DrawerLayout. However, we only have their mobile webpage (with a menu, etc.). So we have to hide some elements. It is very important that the existing stylesheets stay the same, just some other CSS rules are added.
What I've tried so far:
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (!mErrorOccured && !noConnectionAvailable) {
injectCSS();
}
mainActivity.hideLoadingScreen();
}
With this Injection:
// Inject CSS method: read style.css/readmode.css from assets folder
// Append stylesheet to document head
private void injectCSS() {
try {
Activity activity = (Activity) mContext;
SharedPreferences sharPref = activity.getSharedPreferences(Constants.PREFERENCE_NAME, Context.MODE_PRIVATE);
Boolean isReadMode = sharPref.getBoolean(Constants.READMODE_KEY, false);
InputStream inputStream;
if (isReadMode) {
inputStream = activity.getAssets().open("readmode.css");
} else {
inputStream = activity.getAssets().open("style.css");
}
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
inputStream.close();
String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
webView.loadUrl("javascript:(function() {" +
"var parent = document.getElementsByTagName('head').item(0);" +
"var style = document.createElement('style');" +
"style.type = 'text/css';" +
// Tell the browser to BASE64-decode the string into your script !!!
"style.innerHTML = window.atob('" + encoded + "');" +
"parent.appendChild(style);" +
"Android.injectCSS('Works!');})()");
} catch (Exception e) {
e.printStackTrace();
}
}
I've also tried to add a JavaScript Interface that uses the Android.injectCSS('Works!'); of the JavaScript above combined with:
#JavascriptInterface
public void injectCSS(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_LONG).show();
mMainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
mMainFragment.unhideWebView();
}
});
}
And:
public void unhideWebView() {
webView.setVisibility(View.VISIBLE);
}
However, there is always a delay before the elements of the web page are hidden. I've also tried to use Jsoup. First this threw an NetworkOnMainThreadException. After I've tried to use it with an AsyncTask, it was not possible to change the WebView on onPostExecute() because this handling must be on the main thread. Even using a runOnUiThrad() did not help calling loadData() on the WebView with the new loaded data.
Is there any way to inject CSS/JS before the WebView shows up?
Related
I'm trying to display a PDF on Android in a Xamarin.Forms project and it works fine, except for the first time it's loaded where just one blank page appears 9 times out of 10.
The first call is to this function, located in the Android project:
public string HTMLToPDF(string html, string filename) {
//html param is a full html description of the pdf
//filename param is something like "example.pdf"
try {
var dir = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/folder/");
var file = new Java.IO.File(dir + "/" + filename);
if (!dir.Exists())
dir.Mkdirs();
int x = 0;
while (file.Exists())
{
x++;
file = new Java.IO.File(dir + "/" + filename + "( " + x + " )");
}
if (webpage == null)
webpage = new Android.Webkit.WebView(Android.App.Application.Context);
else
webpage.RemoveAllViews();
int width = 2100;
int height = 2970;
webpage.Layout(0, 0, width, height);
webpage.LoadData(html, "text/html", "UTF-8");
webpage.SetWebViewClient(new WebViewCallBack(file.ToString()));
this.Print(webpage, file.ToString(), filename);
return file.ToString();
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Error", e.Message, "Ok");
}
return "";
}
This is what the WebViewCallBack class looks like:
class WebViewCallBack : Android.Webkit.WebViewClient
{
string fileNameWithPath = null;
public WebViewCallBack(string path)
{
this.fileNameWithPath = path;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
view.SetInitialScale(1);
view.Settings.LoadWithOverviewMode = true;
view.Settings.UseWideViewPort = true;
PdfDocument document = new Android.Graphics.Pdf.PdfDocument();
Android.Graphics.Pdf.PdfDocument.Page page = document.StartPage(new Android.Graphics.Pdf.PdfDocument.PageInfo.Builder(2100, 2970, 1).Create());
view.Draw(page.Canvas);
document.FinishPage(page);
Stream filestream = new MemoryStream();
Java.IO.FileOutputStream fos = new Java.IO.FileOutputStream(fileNameWithPath, false);
try
{
document.WriteTo(filestream);
fos.Write(((MemoryStream)filestream).ToArray(), 0, (int)filestream.Length);
fos.Close();
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
}
}
}
And the method Print called at the end:
public void Print(Android.Webkit.WebView webView, string filename, string onlyFileName)
{
try
{
PrintAttributes.Builder builder = new PrintAttributes.Builder();
PrintAttributes.Margins margins = new PrintAttributes.Margins(0, 0, 0, 80);
builder.SetMinMargins(margins);
builder.SetMediaSize(PrintAttributes.MediaSize.IsoA4);
builder.SetColorMode(PrintColorMode.Color);
PrintAttributes attr = builder.Build();
PrintManager printManager = (PrintManager)Forms.Context.GetSystemService(Android.Content.Context.PrintService);
var printAdapter = new GenericPrintAdapter(Forms.Context, webView, filename, onlyFileName);
printAdapter.OnEnded += PrintAdapter_OnEnded;
printAdapter.OnError += PrintAdapter_OnError;
printManager.Print(filename, printAdapter, attr);
}
catch (Java.Lang.Exception e)
{
App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
}
}
When I put a breakpoint on the first line of the OnPageFinished callback of the WebViewCallBack class, I see two different things at this point:
either the PDF interface is up but it's still loading the PDF. In that case, the PDF loads fine once I click on "play" again.
either the PDF has already loaded, as a single blank page. This only happens when it's the first try at loading this particular PDF.
Thus I guess I have to find a way to force the loader to wait for the OnPageFinished method to run first? But that seems wrong.
I can also add that the original HTML contains images, which are all appearing as base64 string in the html string I'm feeding to HTMLToPDF. I noticed that the PDF loads well even on the first try if there are no images in the HTML, so I thought the problem might be that the PDF loads before it's ready on the first try only, maybe because of the images. I couldn't find a fix for that though.
Can anybody shed some light on this for me?
So there are a couple of ways to handle the flow, but I would go with just an event or an Observable.
So lets convert your WebViewCallBack to something that actually performs a callback when its OnPageFinished is called. This is using System.Reactive but you could also use an EventHandler...
// a few class level variables
bool busy;
WebView webView;
IDisposable WhenPageIsLoadedSubscription;
public class WebViewObservable : WebViewClient
{
Subject<string> pageLoaded = new Subject<string>();
public IObservable<string> WhenPageIsLoaded
{
get { return pageLoaded.AsObservable(); }
}
public override void OnPageFinished(WebView view, string url)
{
pageLoaded.OnNext(url);
}
}
Now define your print routine (the calls in your original OnPageFinished and Print method). This will automatically be called when the webpage is finished loading.
void PrintWebPage(WebView webView, string url)
{
Log.Debug("SO", $"Page: {url} loaded, lets print it now" );
// Perform the work that you used to do in OnPageFinished
// Perform the work in your Print method
// Now turn off a Progress indictor if desired
busy = false;
}
Setup your WebView with the observable that calls the PrintWebPage action every time a page is loaded...
void LoadAndPrintWebPage(string html))
{
busy = true;
if (webView == null)
{
int width = 2100;
int height = 2970;
webView = new WebView(this);
var client = new WebViewObservable();
WhenPageIsLoadedSubscription = client.WhenPageIsLoaded.Subscribe((url) => { PrintWebPage(webView, url); });
webView.SetWebViewClient(client);
webView.Layout(0, 0, width, height);
}
webView.LoadData(html, "text/html", "UTF-8");
}
Now call LoadWebPage with your html content and it will be automatically printed after the page is finished loading...
LoadAndPrintWebPage(html);
When you are done, clean up your observable and webview to avoid memory leaks...
void CleanupWebView()
{
WhenPageIsLoadedSubscription?.Dispose();
webView?.Dispose();
webView = null;
}
I had similar problem with blank page. But I used google doc for displaying it. http://docs.google.com/gview?embedded=true&url=
I could solved my problem only by checking operation time between OnPageStarted and OnPageFinished. If it took short time, then something go wrong and page is blank.
here is my webClient.
Every time then page is blank I just reload it. Also add simple circuit breaker just in case
public class MyWebViewClient : WebViewClient
{
private readonly WebView _webView;
private DateTime _dateTime = DateTime.Now;
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
private int _breakerHits;
private const int _BreakerCount = 2;
public MyWebViewClient(WebView webView)
{
_webView = webView;
}
public override async void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
await _semaphoreSlim.WaitAsync();
_dateTime = DateTime.Now;
base.OnPageStarted(view, url, favicon);
_webView.SendNavigating(new WebNavigatingEventArgs(WebNavigationEvent.NewPage, null, url));
_semaphoreSlim.Release();
}
public override async void OnPageFinished(Android.Webkit.WebView view, string url)
{
await _semaphoreSlim.WaitAsync();
base.OnPageFinished(view, url);
if (url.Contains(".pdf"))
{
var diff = DateTime.Now - _dateTime;
if (diff > TimeSpan.FromMilliseconds(700) || _breakerHits > _BreakerCount)
{
_breakerHits = 0;
_webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
WebNavigationResult.Success));
}
else
{
_breakerHits++;
view.Reload();
}
}
else
{
_webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
WebNavigationResult.Success));
}
_semaphoreSlim.Release();
}
}
I have a WebView with custom WebViewClient class, which allowed me to react to user's clicks on links inside the WebView and process them.
Here's the code:
class WebViewClickClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) {
String note = "";
String note_id = new String(url);
if (note_id.contains("#")) {
int offset = note_id.indexOf("#");
note_id = note_id.substring(offset + 1, note_id.length());
}
for (int i = 0; i < bookNotes.size(); i++) {
if (bookNotes.get(i).noteId().equals(note_id)) {
note = bookNotes.get(i).noteSection();
break;
}
}
if (!note.isEmpty()) {
Toast.makeText(getContext(), note, LENGTH_LONG).show();
}
}
Now it's not working anymore and the WebViewClient's methods aren't even triggered upon click. Instead I get this message: I/chromium: [INFO:CONSOLE(0)] "Not allowed to navigate top frame to data URL:"
Do I need to handle this with JavaScript now or is there any workaround with WebView for Android?
UPDATE:
For anyone interested in workaround with JS Interface here's how I made it.
The logic for reacting to user clicks moved from the onPageFinished method to
private class JSInterface {
public void showToast(String key) {...}
}
Then, WebView is created as follows:
WebView book_view;
book_view = (WebView) view.findViewById(R.id.web_text_view);
book_view.getSettings().setJavaScriptEnabled(true);
book_view.addJavascriptInterface(new JSInterface(), "JAVA_CODE");
JAVA_CODE object will be called from JavaScript code which has to be added dynamically to each WebView "page" by concatenating text contents with JS and calling loadData() method on the WebView.
StringBuilder j_script = new StringBuilder();
if (note_ids.size() > 0) {
j_script.append("<script type=\"text/javascript\">\nfunction jscallback()\n{");
for (int i = 0; i < note_ids.size(); i++) {
j_script.append("var dv" + i + " = document.getElementById(\"" + note_ids.get(i) + "\");\ndv" + i + ".onclick = function() {JAVA_CODE.showToast(\"" + note_ids.get(i) + "\")};\n");
}
j_script.append("}</script>");
}
I'm developing an Android application that reads ebooks (in epub format) and as for now I'm using Paul Siegeman's epublib library that is really a very good epub reader but it has some limitations, for example and the one I need, you can't move through pages horizontally (as you do reading a real book) so I need my own implementation of it, but I'm stuck.
The method that actually reads the epub and then puts it inside a webview is the next:
private void openEpub(String bookFilename){
WebView webView = (WebView) findViewById(R.id.webView);
nl.siegmann.epublib.domain.Book book=null;
try {
book = (new EpubReader()).readEpub(new FileInputStream(Environment.getExternalStorageDirectory().getPath() + "/" + bookFilename));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String baseUrl = Environment.getExternalStorageDirectory().getPath() + "/";
String data=null;
try {
data = new String(book.getContents().get(1).getData());
} catch (IOException e) {
e.printStackTrace();
}
webView.loadDataWithBaseURL(baseUrl, data, "text/html", "UTF-8", null);
}
So as you see I display the ebook in a webview so as far as I know the only scrolling possibility webview gives is up/down.
I was thinking on splitting the html string that getData() returns and webview loads into pages and displaying them one by one with a viewpager, but how to split the html correctly according to screen size?
Do you think with this idea I'm on the right way? Any other solutions to display epub from left to right / right to left (paginate) or any other "free or cheap" library to do so? (I tried PageTurner, it's really good, but the commercial version is too expensive for me)
I have done pagination effect in android like this..
-> create a custom webview class.
-> set below clients and load url then you will get horizontal scrolling with page count.
-> Lock the webview default scroll.
-> For smooth pagination effect instead of moving scroll of webview ,move the entire webview so for one page there would be one webview.
-> Use your own viewflippers to buffer previous and next pages.
I have done all these implementations and I made a product for an organisation.Just I am sharing my idea how to approach towards the best solution.Instead of using third parities and stucking in the middle due to limitation of that sdk ,make every thing your own.
private class MyWebClient extends WebViewClient
{
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
#Override
public void onPageFinished(WebView view, String url)
{
super.onPageFinished(view, url);
final MyWebView myWebView = (MyWebView) view;
String varMySheet = "var mySheet = document.styleSheets[0];";
String addCSSRule = "function addCSSRule(selector, newRule) {"
+ "ruleIndex = mySheet.cssRules.length;"
+ "mySheet.insertRule(selector + '{' + newRule + ';}', ruleIndex);"
+ "}";
String insertRule1 = "addCSSRule('html', 'padding: 0px; height: "
+ (myWebView.getMeasuredHeight()/getContext().getResources().getDisplayMetrics().density )
+ "px; -webkit-column-gap: 0px; -webkit-column-width: "
+ myWebView.getMeasuredWidth() + "px;')";
myWebView.loadUrl("javascript:" + varMySheet);
myWebView.loadUrl("javascript:" + addCSSRule);
myWebView.loadUrl("javascript:" + insertRule1);
}
}
private class MyWebChromeClient extends WebChromeClient
{
#Override
public void onProgressChanged(WebView view, int newProgress)
{
super.onProgressChanged(view, newProgress);
if(newProgress == 100)
{
postDelayed(new Runnable()
{
#Override
public void run()
{
calculateNoOfPages();
}
},200);
}
}
}
private void calculateNoOfPages()
{
if(GlobalSettings.EPUB_LAYOUT_TYPE == GlobalConstants.FIXED)
{
}
else
{
if(getMeasuredWidth() != 0)
{
int newPageCount = computeHorizontalScrollRange()/getMeasuredWidth();
getData().getChapterVO().setPageCount(newPageCount);
}
}
}
#Override
public int computeHorizontalScrollRange() {
// TODO Auto-generated method stub
return super.computeHorizontalScrollRange();
}
one you load url to
Follow this github account.
FbReader provide some great libraries for epub and pdf reader. Try this....
or
You can Make your own custom WebView by extending WebView. Here you can place and modify all the functionalities you want from your WebView.
My colleague made a reader using this FbReader and It was fabulous.
Hey I think this will help you. The answer by Nacho L worked for me. Here HTML book-like pagination?
I've started to write an app which provides the user with an HTML form via a WebView. As the form is not under my control, the data filled in may be sent as either GET or POST request. My app is required to capture the transported form data, that is, get a hold on what was entered into the form fields.
Using an adequate callback from WebViewClient such as onPageLoaded(), it is easy to capture form data from a GET request. However, I cannot find any appropriate method to allow the same for POSTed data, i.e., be able to access the HTTP POST message body containing the form data. Am I missing a relevant callback here or is there simply no way to accomplish the specified goal with the given API (even the latest level 8)?
Assuming it wasn't possible, I considered overriding and extending parts of android.webkit in order to introduce a new callback hook that is passed the POST body somehow. That way, my app could be shipped with a customized browser/WebViewClient that fulfills the desired feature. However, I couldn't find any good spot to start with in the code and would be glad for any hints in this regards (in case the approach looks promising at all).
Thanks in advance!
As indicated in my own comment to the original question, the JavaScript injection approach works. Basically, what you need to do is add some piece of JavaScript code to the DOM onsubmit event, have it parse the form's fields, and return the result back to a Java-registered function.
Code example:
public class MyBrowser extends Activity {
private final String jsInjectCode =
"function parseForm(event) {" +
" var form = this;" +
" // make sure form points to the surrounding form object if a custom button was used" +
" if (this.tagName.toLowerCase() != 'form')" +
" form = this.form;" +
" var data = '';" +
" if (!form.method) form.method = 'get';" +
" data += 'method=' + form.method;" +
" data += '&action=' + form.action;" +
" var inputs = document.forms[0].getElementsByTagName('input');" +
" for (var i = 0; i < inputs.length; i++) {" +
" var field = inputs[i];" +
" if (field.type != 'submit' && field.type != 'reset' && field.type != 'button')" +
" data += '&' + field.name + '=' + field.value;" +
" }" +
" HTMLOUT.processFormData(data);" +
"}" +
"" +
"for (var form_idx = 0; form_idx < document.forms.length; ++form_idx)" +
" document.forms[form_idx].addEventListener('submit', parseForm, false);" +
"var inputs = document.getElementsByTagName('input');" +
"for (var i = 0; i < inputs.length; i++) {" +
" if (inputs[i].getAttribute('type') == 'button')" +
" inputs[i].addEventListener('click', parseForm, false);" +
"}" +
"";
class JavaScriptInterface {
#JavascriptInterface
public void processFormData(String formData) {
//added annotation for API > 17 to make it work
<do whatever you need to do with the form data>
}
}
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.browser);
WebView browser = (WebView)findViewById(R.id.browser_window);
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new JavaScriptInterface(), "HTMLOUT");
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { " +
MyBrowser.jsInjectCode + "})()");
}
}
Informally, what this does is inject the custom JavaScript code (as a onsubmit handler) whenever a page finishes loading. On submission of a form, Javascript will parse the form data and pass it back to Java land through the JavaScriptInterface object.
In order to parse form fields, the Javascript code adds form onsubmit and button onclick handlers. The former can handle canonical form submissions through a regular submit button while the latter deals with custom submit buttons, i.e., buttons that do some additional Javascript magic before calling form.submit().
Please be aware that the Javascript code may not be perfect: There might be other methods to submit a form that my injected code may not be able to catch. However, I'm convinced that the injected code can be updated to deal with such possibilities.
The provided answer gives error so I decided to make a simpler implementation which also featured well structured JavaScript (meaning JS is in a file):
In your assets folder create a file called inject.js with following code inside:
document.getElementsByTagName('form')[0].onsubmit = function () {
var objPWD, objAccount, objSave;
var str = '';
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].name.toLowerCase() === 'username') {
objAccount = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'password') {
objPWD = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'rememberlogin') {
objSave = inputs[i];
}
}
if(objAccount != null) {
str += objAccount.value;
}
if(objPWD != null) {
str += ' , ' + objPWD.value;
}
if(objSave != null) {
str += ' , ' + objSave.value;
}
window.AndroidInterface.processHTML(str);
return true;
};
This is the javascript code we'll use for injections, you can switch out the if statements as you see fit and use types instead or names.The callback to Android is this line: window.AndroidInterface.processHTML(str);
Then your Activity/fragment should look like this:
public class MainActivity extends ActionBarActivity {
class JavaScriptInterface {
#JavascriptInterface
public void processHTML(String formData) {
Log.d("AWESOME_TAG", "form data: " + formData);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
this.setContentView(webView);
// enable javascript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
// catch events
webView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
try {
view.loadUrl("javascript:" + buildInjection());
} catch (IOException e) {
e.printStackTrace();
}
}
});
webView.loadUrl("http://someurl.com");
}
private String buildInjection() throws IOException {
StringBuilder buf = new StringBuilder();
InputStream inject = getAssets().open("inject.js");// file from assets
BufferedReader in = new BufferedReader(new InputStreamReader(inject, "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
return buf.toString();
}
I've started to write an app which provides the user with an HTML form via a WebView. As the form is not under my control, the data filled in may be sent as either GET or POST request. My app is required to capture the transported form data, that is, get a hold on what was entered into the form fields.
Using an adequate callback from WebViewClient such as onPageLoaded(), it is easy to capture form data from a GET request. However, I cannot find any appropriate method to allow the same for POSTed data, i.e., be able to access the HTTP POST message body containing the form data. Am I missing a relevant callback here or is there simply no way to accomplish the specified goal with the given API (even the latest level 8)?
Assuming it wasn't possible, I considered overriding and extending parts of android.webkit in order to introduce a new callback hook that is passed the POST body somehow. That way, my app could be shipped with a customized browser/WebViewClient that fulfills the desired feature. However, I couldn't find any good spot to start with in the code and would be glad for any hints in this regards (in case the approach looks promising at all).
Thanks in advance!
As indicated in my own comment to the original question, the JavaScript injection approach works. Basically, what you need to do is add some piece of JavaScript code to the DOM onsubmit event, have it parse the form's fields, and return the result back to a Java-registered function.
Code example:
public class MyBrowser extends Activity {
private final String jsInjectCode =
"function parseForm(event) {" +
" var form = this;" +
" // make sure form points to the surrounding form object if a custom button was used" +
" if (this.tagName.toLowerCase() != 'form')" +
" form = this.form;" +
" var data = '';" +
" if (!form.method) form.method = 'get';" +
" data += 'method=' + form.method;" +
" data += '&action=' + form.action;" +
" var inputs = document.forms[0].getElementsByTagName('input');" +
" for (var i = 0; i < inputs.length; i++) {" +
" var field = inputs[i];" +
" if (field.type != 'submit' && field.type != 'reset' && field.type != 'button')" +
" data += '&' + field.name + '=' + field.value;" +
" }" +
" HTMLOUT.processFormData(data);" +
"}" +
"" +
"for (var form_idx = 0; form_idx < document.forms.length; ++form_idx)" +
" document.forms[form_idx].addEventListener('submit', parseForm, false);" +
"var inputs = document.getElementsByTagName('input');" +
"for (var i = 0; i < inputs.length; i++) {" +
" if (inputs[i].getAttribute('type') == 'button')" +
" inputs[i].addEventListener('click', parseForm, false);" +
"}" +
"";
class JavaScriptInterface {
#JavascriptInterface
public void processFormData(String formData) {
//added annotation for API > 17 to make it work
<do whatever you need to do with the form data>
}
}
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.browser);
WebView browser = (WebView)findViewById(R.id.browser_window);
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new JavaScriptInterface(), "HTMLOUT");
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { " +
MyBrowser.jsInjectCode + "})()");
}
}
Informally, what this does is inject the custom JavaScript code (as a onsubmit handler) whenever a page finishes loading. On submission of a form, Javascript will parse the form data and pass it back to Java land through the JavaScriptInterface object.
In order to parse form fields, the Javascript code adds form onsubmit and button onclick handlers. The former can handle canonical form submissions through a regular submit button while the latter deals with custom submit buttons, i.e., buttons that do some additional Javascript magic before calling form.submit().
Please be aware that the Javascript code may not be perfect: There might be other methods to submit a form that my injected code may not be able to catch. However, I'm convinced that the injected code can be updated to deal with such possibilities.
The provided answer gives error so I decided to make a simpler implementation which also featured well structured JavaScript (meaning JS is in a file):
In your assets folder create a file called inject.js with following code inside:
document.getElementsByTagName('form')[0].onsubmit = function () {
var objPWD, objAccount, objSave;
var str = '';
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].name.toLowerCase() === 'username') {
objAccount = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'password') {
objPWD = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'rememberlogin') {
objSave = inputs[i];
}
}
if(objAccount != null) {
str += objAccount.value;
}
if(objPWD != null) {
str += ' , ' + objPWD.value;
}
if(objSave != null) {
str += ' , ' + objSave.value;
}
window.AndroidInterface.processHTML(str);
return true;
};
This is the javascript code we'll use for injections, you can switch out the if statements as you see fit and use types instead or names.The callback to Android is this line: window.AndroidInterface.processHTML(str);
Then your Activity/fragment should look like this:
public class MainActivity extends ActionBarActivity {
class JavaScriptInterface {
#JavascriptInterface
public void processHTML(String formData) {
Log.d("AWESOME_TAG", "form data: " + formData);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
this.setContentView(webView);
// enable javascript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
// catch events
webView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
try {
view.loadUrl("javascript:" + buildInjection());
} catch (IOException e) {
e.printStackTrace();
}
}
});
webView.loadUrl("http://someurl.com");
}
private String buildInjection() throws IOException {
StringBuilder buf = new StringBuilder();
InputStream inject = getAssets().open("inject.js");// file from assets
BufferedReader in = new BufferedReader(new InputStreamReader(inject, "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
return buf.toString();
}