I'm trying to save a page html code into a string, then load it to a webview, I managed to get full HTML code successfully, however when I try to load it into webview, somehow it webview remove all content in
Here is how I initialized webView:
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setDomStorageEnabled(true);
webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
webView.getSettings().setGeolocationEnabled(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.setWebViewClient(new MyWebViewClient());
Webclient:
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
siteAddressBar.setText(url);
view.loadUrl(url);
Site = url;
return true;
}
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onPageFinished(WebView view, String url) {
//view.loadUrl();
view.evaluateJavascript("(function() {return document.getElementsByTagName('html')[0].outerHTML;})();", new ValueCallback<String>() {
#Override
public void onReceiveValue(final String value) {
JsonReader reader = new JsonReader(new StringReader(value));
reader.setLenient(true);
try {
if (reader.peek() == JsonToken.STRING) {
String domStr = reader.nextString();
if (domStr != null) {
htmlCode = domStr;
Log.e("Stonmrigal1",domStr);
}
}
} catch (IOException e) {
// handle exception
} finally {
//IoUtil.close(reader);
}
}
});
}
}
and Then tried to load html like this:
webView.loadData(htmlCode, "text/html; charset=utf-8", "UTF-8");
I get response like this:
<html class="js backgroundsize borderimage csstransitions fontface svg details progressbar meter no-mathml cors xxsmallview wb-enable" dir="ltr" lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body></body>
</html>
Solved, I used
webView.loadDataWithBaseURL(null, content, "text/html", "UTF-8", null);
I am loading a webpage in WebView. There is a link in the webpage, which on desktop will download the file, but in the app the link should display a Toast saying the link is disabled for the app.
I am not sure how to get the value from href of the anchor tag, when the link is clicked.
<a class="btn btn-primary" download="http://xx.xxx.com/wp-content/uploads/2015/11/abc-27-15.mp3" href="http://xx.xxx.com/wp-content/uploads/2015/11/abc-27-15.mp3">
<i class="fa fa-download"></i> Download Audio</a>
Can someone share an idea or any sample code on how to do this.
EDIT:1
Here is what I am doing currently:
private static final String URL = "http://xx.xxx.com/wp-content/uploads/";
webView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
WebView.HitTestResult hr = ((WebView) v).getHitTestResult();
String extra = hr.getExtra();
if (extra != null && extra.startsWith(URL) && extra.endsWith(".mp3")) {
Log.d("WebviewActivity", "Extra: " + extra);
Log.d("WebviewActivity", "Contains URL");
return true;
}
}
return false;
}
});
The problem with this approach is:
When i click on the link, i get the url in extra. It works fine till here. But, from next time, no matter where i click on the webview, the same extra is being returned. So even if i click on an image after i click on the url, i get the same url in the extra. Not sure if i doing anything wrong. Or is this the correct approach.
Please let me know if you need any details.
EDIT:2
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
// Get link-URL.
String url = (String) msg.getData().get("url");
// Do something with it.
if (url != null) {
Log.d(TAG, "URL: "+url);
}
}
};
webView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
WebView.HitTestResult hr = ((WebView) v).getHitTestResult();
if (hr.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
Message msg = mHandler.obtainMessage();
webView.requestFocusNodeHref(msg);
}
}
return false;
}
});
webView.loadUrl(mUrl);
}
Now, i get the URL that is clicked in the last action_down event. How to get the current URL?
EDIT 3 (Attempt with webviewclient:
private class MyWebViewClient extends WebViewClient {
private static final String URL = "xx.xxx.com/wp-content/uploads/";
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
if (!isFinishing())
mProgressDialog.show();
}
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressDialog.dismiss();
}
#Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
Toast.makeText(WebviewActivity.this,
"Please check your internet " + "connection and try again",
Toast.LENGTH_SHORT).show();
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d("xxx", "Url: " + url);
if(url.contains(URL)) {
Log.d("xxx", "Url Contains: " + URL);
return true;
}
return false;
}
}
mMyWebViewClient = new MyWebViewClient();
webView.setWebViewClient(mMyWebViewClient);
Output in logcat when the link is clicked:
03-01 15:38:19.402 19626-19626/com.xx.xxx D/cr_Ime: [ImeAdapter.java:553] focusedNodeChanged: isEditable [false]
03-01 15:38:19.428 19626-19626/com.xx.xxx D/cr_Ime: [ImeAdapter.java:253] updateKeyboardVisibility: type [0->0], flags [0], show [true],
03-01 15:38:19.428 19626-19626/com.xx.xxx D/cr_Ime: [ImeAdapter.java:326] hideKeyboard
03-01 15:38:19.429 19626-19626/com.xx.xxx D/cr_Ime: [InputMethodManagerWrapper.java:56] isActive: true
03-01 15:38:19.429 19626-19626/com.xx.xxx D/cr_Ime: [InputMethodManagerWrapper.java:65] hideSoftInputFromWindow
Because you are using a WebView and the link is not Java script this is very easy to achieve with a WebViewClient which you can use to monitor your WebView
myWebView.setWebViewClient( new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// check something unique to the urls you want to block
if (url.contains("xx.xxx.com")) {
Toast.make... //trigger the toast
return true; //with return true, the webview wont try rendering the url
}
return false; //let other links work normally
}
} );
It's possible that because your URL ends in .mp3 the file is being treated as a resource. You should also override the shouldInterceptRequest method of the WebViewClient to check this.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
Log.d("XXX", "Url from API21 shouldInterceptRequest : " + url);
if (url.contains(URL)) {
return new WebResourceResponse("text/html", "UTF-8", "<html><body>No downloading from app</body></html>");
} else {
return null;
}
}
public WebResourceResponse shouldInterceptRequest (WebView view, String url) {
Log.d("XXX", "Url from shouldInterceptRequest : " + url);
if (url.contains(URL)) {
return new WebResourceResponse("text/html", "UTF-8", "<html><body>No downloading from app</body></html>");
} else {
return null;
}
}
Most of the work can be done at the web page side itself. You have to write java script to identify which device is accessing the page (mobile, desktop etc) if its mobile then use java script binding technique to call the native android code to show Toast message.
http://developer.android.com/guide/webapps/webview.html
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
WebAppInterface.java
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
YourHTML Page (this sample got a button click)
<input type="button" value="Say hello" onClick="showAndroidToast('Hello
Android!')" />
<script type="text/javascript">
function showAndroidToast(toast) {
Android.showToast(toast);
}
</script>
I have a div called downloadapp that I'd like to display to users who visit my website with the browser of their smartphone instead of using my Android app. So I need to hide that div for users who already use my app.
First I used onPageStarted and onPageFinished but visitors kept seeing the div for a few seconds before it disappears. Then someone gave me the advice to add an identifying string (e.g: "my app") into the app's user agent string with the below result. Unfortunately the div still won't disappear from the beginning in my app so what am I missing here?
Webpage's html + js code:
<head>
<script>
if (navigator.userAgent.endsWith("myapp")) {
document.getElementById("downloadapp").style.display = "none";
} else {
document.getElementById("downloadapp").style.display = "inherit";
}
</script>
</head>
<body>
<div id="downloadapp">
<img src="/example.png">
</div>
</body>
Code Android Webview:
private void startWebView(String url) {
webView.getSettings().setUserAgentString(webView.getSettings().getUserAgentString() + "; myapp");
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
//On error, open local file
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
webView.loadUrl("file:///android_asset/www/myerrorpage.html");
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
view.loadUrl("javascript:document.getElementById('downloadapp').style.display = 'none'; void(0);");
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:document.getElementById('downloadapp').style.display = 'none'; void(0);");
}
});
webView.loadUrl(url);
}
You made mistake when set user agent. Replace this:
webView.getSettings().setUserAgentString(webView.getSettings().getUserAgentString() + "; myapp");
With This
webView.getSettings().setUserAgentString("myapp");
Below code, I am using to load webview in fragment and I want to call JavaScript function from webview but below code is not working.
public class MainActivity extends ActionBarActivity
{
public void loadUrlInWebView(String url,String positon)
{
WebViewFragment fragment = new WebViewFragment();
Bundle data = new Bundle();
data.putString("url",url);
fragment.setArguments(data);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.frame_container, fragment).commit();
}
var url ="javascript:mobileApp.openLoginMenu()";
loadUrlInWebView(url,null);
}
public class WebViewFragment extends Fragment{
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
url = getArguments().getString("url");
View rootView = inflater.inflate(R.layout.activity_main, container, false);
webView = (WebView) rootView.findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsInterface(getActivity()),"AndroidJSObject");
webView.loadUrl(url);
webView.setWebViewClient(new WebViewClient()
{
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
view.loadUrl(url);
return true;
}
});
return rootView;
}
}
To call a javascript into android you can call addJavascriptInterface().
For more explanation please follow this link Building Web Apps in WebView.
And the thing to keep in mind is,
When you want to call an android method from javascript method, the method name should be called on the string name you have provided while calling addJavascriptInterface() precisely in your case something like this in your javascript
AndroidJSObject.yourmethod
Inside javascript file.
if android:targetSdkVersion >=17
add #SuppressLint("JavascriptInterface") in onCreated method
and
class InJavaScript {
#JavascriptInterface
public void runOnAndroidJavaScript(String status) {
collectionStatus.setCollectionStatus(status);
}
}
watch out #JavasscriptInterface.
I am just trying to implement the facebook login on a WebView in Android. The problem is after I click the facebook button on my HTML page and insert the username and password on Facebook dialog. The url redirect is just giving me a black page.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webview = new WebView(this);
webview.setWebChromeClient(new WebChromeClient());
webview.getSettings().setPluginState(PluginState.ON);
webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
webview.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webview.setWebViewClient(new WebViewClient());
webview.loadUrl("http://peoplehunt.crowdscanner.com/hunt");
setContentView(webview);
This is the Facebook regular javascript API on my HTML page and this function gets called when the facebook button is clicked.
$("#login_facebook").click(function() {
FB.login(function(response) {
//This function should be called
if (response.authResponse) {
FB.api('/me?fields=name,email,picture,id&type=large', function(response) {
//console.log("email "+response.email);
$("#submitHandle").hide();
$("#loader").show();
//console.log('Good to see you, ' + response.picture + '.');
var theUsername = response.name;
theUsername = theUsername.replace(/ /g, '_')+"_"+response.id+"#meetforeal.com";
//console.log(theUsername);
$("input[name=email]").val(encodeURIComponent(response.email));
$("input[name=lastName]").val(encodeURIComponent(response.name));
$("input[name=avatarImage]").val(response.picture);
$("input[name=userName]").val(encodeURIComponent(theUsername));
$("#msg_twitter").fadeIn("slow");
$("#submitHandle").show();
$("#loader").hide();
$("#user").attr("action","/crowdmodule/auth/registerattendeefacebook");
$("#user").submit();
});
} else {
//console.log('User cancelled login or did not fully authorize.');
}
}, {scope: 'email'});
ANY ideas on how to get the response back after the redirect on the Facebook dialog page? THANKS.
I had the same issue on my android application.
The cause of the issue is FB login javascript opens a new page on a new window. Then it tries to close it and send some javascript auth codes back, after login success. WebView is typically "single window only" so it has no place to go back to, hence the blank screen.
Please follow flowing example from my working codes.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".MyActivity"
android:id="#+id/webview_frame">
<WebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
The Webview of id "webview" is the main view for my content.
Below is my activity codes.
public class MyActivity extends Activity {
/* URL saved to be loaded after fb login */
private static final String target_url="http://www.example.com";
private static final String target_url_prefix="www.example.com";
private Context mContext;
private WebView mWebview;
private WebView mWebviewPop;
private FrameLayout mContainer;
private long mLastBackPressTime = 0;
private Toast mToast;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_urimalo);
// final View controlsView =
// findViewById(R.id.fullscreen_content_controls);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
mWebview = (WebView) findViewById(R.id.webview);
//mWebviewPop = (WebView) findViewById(R.id.webviewPop);
mContainer = (FrameLayout) findViewById(R.id.webview_frame);
WebSettings webSettings = mWebview.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setSupportMultipleWindows(true);
mWebview.setWebViewClient(new UriWebViewClient());
mWebview.setWebChromeClient(new UriChromeClient());
mWebview.loadUrl(target_url);
mContext=this.getApplicationContext();
}
private class UriWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String host = Uri.parse(url).getHost();
//Log.d("shouldOverrideUrlLoading", url);
if (host.equals(target_url_prefix))
{
// This is my web site, so do not override; let my WebView load
// the page
if(mWebviewPop!=null)
{
mWebviewPop.setVisibility(View.GONE);
mContainer.removeView(mWebviewPop);
mWebviewPop=null;
}
return false;
}
if(host.equals("m.facebook.com"))
{
return false;
}
// Otherwise, the link is not for a page on my site, so launch
// another Activity that handles URLs
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
Log.d("onReceivedSslError", "onReceivedSslError");
//super.onReceivedSslError(view, handler, error);
}
}
class UriChromeClient extends WebChromeClient {
#Override
public boolean onCreateWindow(WebView view, boolean isDialog,
boolean isUserGesture, Message resultMsg) {
mWebviewPop = new WebView(mContext);
mWebviewPop.setVerticalScrollBarEnabled(false);
mWebviewPop.setHorizontalScrollBarEnabled(false);
mWebviewPop.setWebViewClient(new UriWebViewClient());
mWebviewPop.getSettings().setJavaScriptEnabled(true);
mWebviewPop.getSettings().setSavePassword(false);
mWebviewPop.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mContainer.addView(mWebviewPop);
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(mWebviewPop);
resultMsg.sendToTarget();
return true;
}
#Override
public void onCloseWindow(WebView window) {
Log.d("onCloseWindow", "called");
}
}
}
The key for this issue is onCreateWindow. A new window is created and inserted to the frame layout and removed upon success. I added the removal at shouldOverrideUrlLoading.
Here is an example Android project:
Github: Android_Popup_Webview_handler_example
This is an Android Studio project showing how to handle popups in Android Webview.
Most open Source browsers do not support opening popups.
Popups are especially important in OAuth login used in a lot of websites (e.g., www.feedly.com).
The popups in this project opened in a dialog and can be dismissed by a close button or pressing Back or if the popup window closes itself (like what happens on most login authentication flow).
Regarding the best answer to this question, you just need to implement the onPageFinished method of the WebViewClient class that you use.
public void onPageFinished(WebView view, String url) {
// First, get the URL that Facebook's login button is actually redirecting you to.
// It should be something simulator to https://www.facebook.com/dialog/return/arbiter?relation=opener&close=true
String webUrl = webView.getUrl();
// Pass it to the LogCat so that you can then use it in the if statement below.
Log.d.println(TAG, webUrl);
if (url.startsWith("The url that you copied from the LogCat")) {
// Check whether the current URL is the URL that Facebook's redirecting you to.
// If it is - that's it - do what you want to after the logging process has finished.
return;
}
super.onPageFinished(view, url);
}
It worked just fine for me. Hope it helps you too :)
Override shouldOverrideUrlLoading() in your WebViewClient. Search for shouldOverrideUrlLoading here. Also, there is a parameter you can pass to facebook's login API; I think it's redirect_uri. That should help you recognize when the login is successful and in your shouldOVerrideUrlLoading(), you would just have to detect the url being loaded and if it is the redirect_uri you specified, then just return true in that method and close the webview or whatever you want to when the login is successful.
Above answer is too old and it does not work with latest Facebook sdk version 2.7. After spending 4 hours on it, I have found out few changes in it. Following code will work fine with latest SDK.
Below mentioned is the XML layout file.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".MainActivity"
android:id="#+id/webview_frame">
<WebView
android:id="#+id/webView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>
This is the Android Activity code snippet
public class MainActivity extends AppCompatActivity {
private WebView webView;
private WebView mWebviewPop;
private FrameLayout mContainer;
private Context mContext;
private String url = "https://www.YourWebsiteAddress.com";
private String target_url_prefix = "www.YourWebsiteAddress.com";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Get outer container
mContainer = (FrameLayout) findViewById(R.id.webview_frame);
webView = (WebView)findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setSupportMultipleWindows(true);
//These two lines are specific for my need. These are not necessary
if (Build.VERSION.SDK_INT >= 21) {
webSettings.setMixedContentMode( WebSettings.MIXED_CONTENT_ALWAYS_ALLOW );
}
//Cookie manager for the webview
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
webView.setWebViewClient(new MyCustomWebViewClient());
webView.setWebChromeClient(new UriWebChromeClient());
webView.loadUrl("https://www.YourWebsiteAddress.com");
mContext=this.getApplicationContext();
}
#Override
public void onBackPressed() {
if(webView.isFocused() && webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
private class MyCustomWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String host = Uri.parse(url).getHost();
if( url.startsWith("http:") || url.startsWith("https:") ) {
if(Uri.parse(url).getPath().equals("/connection-compte.html")) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.YourWebsiteAddress.com"));
startActivity(browserIntent);
return true ;
}
if (host.equals(target_url_prefix)) {
if (mWebviewPop != null) {
mWebviewPop.setVisibility(View.GONE);
mContainer.removeView(mWebviewPop);
mWebviewPop = null;
}
return false;
}
if (host.equals("m.facebook.com") || host.equals("www.facebook.com") || host.equals("facebook.com")) {
return false;
}
// Otherwise, the link is not for a page on my site, so launch
// another Activity that handles URLs
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
// Otherwise allow the OS to handle it
else if (url.startsWith("tel:")) {
Intent tel = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(tel);
return true;
}
//This is again specific for my website
else if (url.startsWith("mailto:")) {
Intent mail = new Intent(Intent.ACTION_SEND);
mail.setType("application/octet-stream");
String AdressMail = new String(url.replace("mailto:" , "")) ;
mail.putExtra(Intent.EXTRA_EMAIL, new String[]{ AdressMail });
mail.putExtra(Intent.EXTRA_SUBJECT, "");
mail.putExtra(Intent.EXTRA_TEXT, "");
startActivity(mail);
return true;
}
return true;
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
Log.d("onReceivedSslError", "onReceivedSslError");
//super.onReceivedSslError(view, handler, error);
}
#Override
public void onPageFinished(WebView view, String url) {
if(url.startsWith("https://m.facebook.com/v2.7/dialog/oauth")){
if(mWebviewPop!=null)
{
mWebviewPop.setVisibility(View.GONE);
mContainer.removeView(mWebviewPop);
mWebviewPop=null;
}
view.loadUrl("https://www.YourWebsiteAddress.com");
return;
}
super.onPageFinished(view, url);
}
}
private class UriWebChromeClient extends WebChromeClient {
#Override
public boolean onCreateWindow(WebView view, boolean isDialog,
boolean isUserGesture, Message resultMsg) {
mWebviewPop = new WebView(mContext);
mWebviewPop.setVerticalScrollBarEnabled(false);
mWebviewPop.setHorizontalScrollBarEnabled(false);
mWebviewPop.setWebViewClient(new MyCustomWebViewClient());
mWebviewPop.getSettings().setJavaScriptEnabled(true);
mWebviewPop.getSettings().setSavePassword(false);
mWebviewPop.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mContainer.addView(mWebviewPop);
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(mWebviewPop);
resultMsg.sendToTarget();
return true;
}
#Override
public void onCloseWindow(WebView window) {
Log.d("onCloseWindow", "called");
}
}
}
My answer is essentially similar to some others here, in that I create a second WebView to host the Facebook login page, rather than attempting to solve the problem with redirects. However, I chose to place the login WebView in its own Fragment, and give it is own dedicated WebViewClient and WebChromeClient subclasses. I think this makes it a little easier to see what role each component plays, and which objects need which settings and behaviors.
I also make use of WebChromeClient.onCloseWindow() to detect when Facebook's JavaScript wants to close the login window. This is much more robust than the approach I originally pursued, from a different answer.
In your Activity layout, you'll have the "primary" WebView, which hosts the comments, and a container for the FacebookWebLoginFragment. The login Fragment is created on-the-fly, when it's needed, and then removed when Facebook's login JavaScript requests that its window be closed.
My Activity layout looks like this:
<include layout="#layout/toolbar_common" />
<FrameLayout
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/web_view_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
/>
<!-- Used for Facebook login associated with comments -->
<FrameLayout
android:id="#+id/facebook_web_login_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:visibility="gone"
/>
</FrameLayout>
Within your Activity, you'll need code to show and hide the Facebook web login fragment. I use the Otto event bus, so I have event handlers like the ones below. (Nothing here is specific to this problem; I include this code just to give you a sense of how the login Fragment fits into the overall structure.)
#Subscribe
public void onShowFacebookWebLoginEvent(ShowFacebookWebLoginEvent event) {
FacebookWebLoginFragment existingFragment = getFacebookWebLoginFragment();
if (existingFragment == null) {
mFacebookWebLoginFragmentContainer.setVisibility(View.VISIBLE);
createFacebookWebLoginFragment(event);
}
}
#Subscribe
public void onHideFacebookWebLoginEvent(HideFacebookWebLoginEvent event) {
FacebookWebLoginFragment existingFragment = getFacebookWebLoginFragment();
if (existingFragment != null) {
mFacebookWebLoginFragmentContainer.setVisibility(View.GONE);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.remove(existingFragment)
.commit();
}
}
#Nullable
private FacebookWebLoginFragment getFacebookWebLoginFragment() {
FragmentManager fm = getSupportFragmentManager();
return (FacebookWebLoginFragment) fm.findFragmentById(R.id.facebook_web_login_fragment_container);
}
private void createFacebookWebLoginFragment(ShowFacebookWebLoginEvent event) {
FragmentManager fm = getSupportFragmentManager();
FacebookWebLoginFragment fragment = (FacebookWebLoginFragment) fm.findFragmentById(R.id.facebook_web_login_fragment_container);
if (fragment == null) {
fragment = FacebookWebLoginFragment.newInstance(event.getOnCreateWindowResultMessage());
fm.beginTransaction()
.add(R.id.facebook_web_login_fragment_container, fragment)
.commit();
}
}
While the FacebookWebLoginFragment is around, it should be given authority to handle device back-button presses. This is important because the Facebook login flow includes the ability to navigate away from the login page, and the user will expect the back button to return them to login. So, in my Activity, I have this:
#Override
public void onBackPressed() {
boolean handled = false;
FacebookWebLoginFragment facebookWebLoginFragment = getFacebookWebLoginFragment();
if (facebookWebLoginFragment != null) {
handled = facebookWebLoginFragment.onBackPressed();
}
if (!handled) {
WebViewFragment fragment = getWebViewFragment();
if (fragment != null) {
handled = fragment.onBackPressed();
}
}
if (!handled) {
finish();
}
}
The layout for FacebookWebLoginFragment is extremely simple:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="#+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
Here's the FacebookWebLoginFragment code. Note that it relies on a subclass of WebChromeClient to detect when the Facebook login JavaScript is ready to close the window (i.e., remove the fragment). Note also that there is no direct communication between this login WebView and the primary WebView, which contains the comments UI; the auth token is passed along via a third-party cookie, which is why you have to be sure to enable third-party cookie support on your primary WebView.
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Message;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import butterknife.Bind;
import butterknife.ButterKnife;
/**
* Hosts WebView used by Facebook web login.
*/
public class FacebookWebLoginFragment extends BaseFragment {
private static final String LOGTAG = LogHelper.getLogTag(FacebookWebLoginFragment.class);
#Bind(R.id.web_view) WebView mFacebookLoginWebView;
private WebChromeClient mFacebookLoginWebChromeClient;
private Message onCreateWindowResultMessage;
public static FacebookWebLoginFragment newInstance(Message onCreateWindowResultMessage) {
FacebookWebLoginFragment fragment = new FacebookWebLoginFragment();
fragment.onCreateWindowResultMessage = onCreateWindowResultMessage;
return fragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.frag_facebook_web_login, container, false);
ButterKnife.bind(this, rootView);
return rootView;
}
#Override
public void onViewCreated(View v, #Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
mFacebookLoginWebView.setVerticalScrollBarEnabled(false);
mFacebookLoginWebView.setHorizontalScrollBarEnabled(false);
mFacebookLoginWebView.setWebViewClient(new FacebookLoginWebViewClient());
mFacebookLoginWebView.getSettings().setJavaScriptEnabled(true);
mFacebookLoginWebView.getSettings().setSavePassword(false);
mFacebookLoginWebView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mFacebookLoginWebChromeClient = makeFacebookLoginWebChromeClient();
mFacebookLoginWebView.setWebChromeClient(mFacebookLoginWebChromeClient);
WebView.WebViewTransport transport = (WebView.WebViewTransport) onCreateWindowResultMessage.obj;
transport.setWebView(mFacebookLoginWebView);
onCreateWindowResultMessage.sendToTarget();
onCreateWindowResultMessage = null; // This seems to eliminate a mysterious crash
}
#Override
public void onDestroy() {
mFacebookLoginWebChromeClient = null;
super.onDestroy();
}
/**
* Performs fragment-specific behavior for back button, and returns true if the back press
* has been fully handled.
*/
public boolean onBackPressed() {
if (mFacebookLoginWebView.canGoBack()) {
mFacebookLoginWebView.goBack();
} else {
closeThisFragment();
}
return true;
}
private void closeThisFragment() {
EventBusHelper.post(new HideFacebookWebLoginEvent());
}
class FacebookLoginWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Only allow content from Facebook
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if (scheme != null && (TextUtils.equals(scheme, "http") || TextUtils.equals(scheme, "https"))) {
if (UriHelper.isFacebookHost(uri)) {
return false;
}
}
return true;
}
}
private WebChromeClient makeFacebookLoginWebChromeClient() {
return new WebChromeClient() {
#Override
public void onCloseWindow(WebView window) {
closeThisFragment();
}
};
}
}
Now, the trickiest bit is making the changes necessary to your existing WebView, since it's likely you've already got a fair amount of code in place around it, and you'll need to make sense of what needs to change.
First, make sure you have JavaScript enabled, and that it supports multiple windows.
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportMultipleWindows(true);
You do not need to call setJavaScriptCanOpenWindowsAutomatically(true).
Looking at some of the other answers, you might think you need to monkey with the WebViewClient that's assigned to your WebView, and override shouldOverrideUrlLoading(). This is not necessary. What's important is the WebChromeClient, which needs to override onCreateWindow().
So... next, assign a custom WebChromeClient subclass to your WebView:
mWebView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
String url = null;
Message href = view.getHandler().obtainMessage();
if (href != null) {
view.requestFocusNodeHref(href);
url = href.getData().getString("url");
}
LogHelper.d(LOGTAG, "onCreateWindow: " + url);
// Unfortunately, url is null when "Log In to Post" button is pressed
if (url == null || UriHelper.isFacebookHost(Uri.parse(url))) {
// Facebook login requires cookies to be enabled, and on more recent versions
// of Android, it's also necessary to enable acceptance of 3rd-party cookies
// on the WebView that hosts Facebook comments
CookieHelper.setAcceptThirdPartyCookies(mWebView, true);
EventBusHelper.post(new ShowFacebookWebLoginEvent(resultMsg));
} else {
LogHelper.d(LOGTAG, "Ignoring request from js to open new window for URL: " + url);
}
return true;
}
});
You'll notice this is the second call to UriHelper.isFacebookHost(). I don't have a bullet-proof approach to determining this, but here's what I do:
public static boolean isFacebookHost(Uri uri) {
if (uri != null && !TextUtils.isEmpty(uri.getHost())) {
String host = uri.getHost().toLowerCase();
return host.endsWith("facebook.com") || host.endsWith("facebook.net");
}
return false;
}
You'll also notice the call to CookieHelper.setAcceptThirdPartyCookies(). Here's the code for that:
public static void setAcceptThirdPartyCookies(WebView webView, boolean accept) {
CookieManager cookieManager = CookieManager.getInstance();
// This is a safeguard, in case you've disabled cookies elsewhere
if (accept && !cookieManager.acceptCookie()) {
cookieManager.setAcceptCookie(true);
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, accept);
}
}
One more thing that trips some people up is the configuration of "Valid OAuth redirect URIs" in the Facebook dev settings. If you see an error like this in your logs:
URL Blocked: This redirect failed because the redirect URI is not whitelisted in the app’s Client OAuth Settings. Make sure Client and Web OAuth Login are on and add all your app domains as Valid OAuth Redirect URIs.
... then you'll want to check out this answer: https://stackoverflow.com/a/37009374
Have fun! A complicated solution to what seems like a pretty simple problem. On the positive side, Android has given developers a ton of control here.
This code works for me! I have a LIKE, SHARE, Comments button on my page.
private lateinit var myWebView: WebView;
private val target_url = "https://www.webkomph.com"
private val target_url_prefix = "webkomph.com"
private var mContext: Context? = null
private var mWebviewPop: WebView? = null
private var mContainer: ConstraintLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
val cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true)
myWebView = findViewById(R.id.webkom_site);
mContainer = findViewById(R.id.webview_frame);
val webkomBlue = Color.parseColor("#1c71bc")
myWebView.setBackgroundColor(webkomBlue);
myWebView.background
val webSettings = myWebView.settings;
webSettings.setJavaScriptEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.javaScriptCanOpenWindowsAutomatically = true;
webSettings.setSupportMultipleWindows(true)
myWebView.setWebViewClient(UriWebViewClient())
myWebView.setWebChromeClient(UriChromeClient())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(myWebView, true)
}
myWebView.loadUrl(target_url);
mContext = this.applicationContext
}
override fun onBackPressed() {
if (myWebView.canGoBack()) {
myWebView.goBack()
} else {
super.onBackPressed()
}
}
inner class UriWebViewClient : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
url: String
): Boolean {
val host = Uri.parse(url).host
//Log.d("shouldOverrideUrlLoading", url);
if(host == null || host == target_url_prefix) {
// This is my web site, so do not override; let my WebView load
// the page
if (mWebviewPop != null) {
mWebviewPop!!.setVisibility(View.GONE)
mContainer?.removeView(mWebviewPop)
mWebviewPop = null
}
return false
}
if (host == "m.facebook.com" || host == "www.facebook.com" || url.contains("ret=login")) {
return false
}
// Otherwise, the link is not for a page on my site, so launch
// another Activity that handles URLs
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
return true
}
override fun onPageFinished(view: WebView?, url: String) {
// Facebook redirects to this url once a user has logged in, this is a blank page so we override this
// http://www.facebook.com/connect/connect_to_external_page_widget_loggedin.php?............
if (url.contains("plugins/close_popup.php?reload") || url.contains("dialog/close_window") || url.contains("facebook.com/dialog/plugin.optin?")) {
if(mWebviewPop != null){
mWebviewPop!!.visibility = View.GONE
mContainer!!.removeView(mWebviewPop)
mWebviewPop = null
if(myWebView != null && (url.contains("dialog/close_window") || url.contains("facebook.com/dialog/plugin.optin?"))){
myWebView?.reload();
}
}
return
}
super.onPageFinished(view, url)
}
override fun onReceivedSslError(
view: WebView, handler: SslErrorHandler,
error: SslError
) {
Log.d("onReceivedSslError", "onReceivedSslError")
//super.onReceivedSslError(view, handler, error);
}
}
inner class UriChromeClient : WebChromeClient() {
override fun onCreateWindow(
view: WebView, isDialog: Boolean,
isUserGesture: Boolean, resultMsg: Message
): Boolean {
mWebviewPop = WebView(mContext)
mWebviewPop!!.setVerticalScrollBarEnabled(false)
mWebviewPop!!.setHorizontalScrollBarEnabled(false)
mWebviewPop!!.setWebViewClient(UriWebViewClient())
mWebviewPop!!.getSettings().setJavaScriptEnabled(true)
mWebviewPop!!.getSettings().setSavePassword(false)
mWebviewPop!!.layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
mContainer?.addView(mWebviewPop)
val transport = resultMsg.obj as WebViewTransport
transport.webView = mWebviewPop
resultMsg.sendToTarget()
return true
}
override fun onCloseWindow(window: WebView) {
Log.d("onCloseWindow", "called")
}
}
Try moving your webview to xml layout file. The blank page error was caused due to js script fail while redirecting oAuth login to authorization acceptance page. You can overcome this issue by moving your webview into xml layout.
Probably not an always viable answer, but another option is to switch from "popup then JS" style OAuth login to the non-popup "redirect_uri" OAUth style, where it sends them to the login page, then after success/failure they get sent to "some uri err other" (ex: another page on your own website) that completes the authentication. FWIW.
FWIW where facebook says "if you're doing WebView redirect them to https://www.facebook.com/connect/login_success.html" my hunch is that that's for the case of (only having a single WebView and) using OAuth to login, so they'd add some auth params to the login_success.html that you could then glean, so not the normal web flow...
Another possibility might be to override the javascript postMessage function so you can capture what it was about to return to the parent window.