Only after visiting almost all Stack Overflow links for the same answer, I was compelled to write this,
I am trying to display a PDF file in the WebView. I am aware that WebView doesn't support displaying PDF normally so I am using the G-Drive URL extension suggested in other answers. Also, the Business team has demanded to open the PDF in our App itself so I can't fire an Intent to other PDF Viewer. I downloaded the PdfViewer library but it increased the APK size far beyond desire.
This URL is actually a GET request. Every time I hit the request, depending on the request parameters the server generates the PDF at runtime and returns it immediately without storing it on the server.
PDF URL:
"http://server_ip_number/pata/retail/v4/reports_pdf?search=Test&min_amount=0.0&max_amount=0.0" (can't disclose IP address for security reasons)
This request also demands some headers for auth, which I'll post below in detail.
This is what I am doing currently;
class AllUsersWebViewActivity : BaseActivity() {
private val pdfActualUrl = "https://drive.google.com/viewerng/viewer?embedded=true&url=http://server_ip_number/pata/retail/v4/reports_pdf?search=Test&min_amount=0.0&max_amount=0.0"
//This random dummy URL opens perfectly, it does not need any headers or GET params, just normal `loadUrl()` works.
private val workingDummyUrl = "https://drive.google.com/viewerng/viewer?embedded=true&url=https://mindorks.s3.ap-south-1.amazonaws.com/courses/MindOrks_Android_Online_Professional_Course-Syllabus.pdf"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_all_users_report_web_view)
wv_web.settings.javaScriptEnabled = true
wv_web.clearCache(true)
wv_web.loadUrl(URLEncoder.encode(pdfUrl, "ISO-8859-1"), mapOf( //map of necessary headers
"Authorization" to "Auth_TOKEN",
"VersionId" to "1.0.0",
"companyID" to "2",
"DeviceId" to "my_device_id",
"token" to "SECURE_TOKEN"
))
}
NOTE:
When I hit the above mentioned GET request with headers and params on PostMan it immediately downloads the PDF file.
I tried the same GET request with DownloadManager with all the headers and params the PDF file is downloaded successfully
But when I try it with WebView it just shows No Preview Available.
What am I doing wrong?
The headers that you are supplying, at most, will affect the behavior of Google's Web server for the https://drive.google.com/viewerng/viewer request. Google is not going to pass those along to some other server for http://server_ip_number/pata/retail/v4/reports_pdf.
Related
I am using Android Web View in my Xamarin Project to perform third party authentication. Once the login is successful I need to extract the authentication cookies. This cookies I am storing in persistent storage and then I am using them for passing to subsequent requests.
For example:
Android App >(opens) webview > Loads (idp provider) url > User provides credentials and saml request is sent to my backend server > backend server validates saml and returns authentication cookies.
It returns two cookies.
Now everything works fine. And in OnPageFinished method of the WebClient of webview I am trying to extract the cookies using the method.
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
var handler = OnPageCompleted;
var uri = new Uri(url);
AllowCookies(view);
var cookies = CookieManager.Instance.GetCookie(url);
var onPageCompletedEventArgs = new OnPageCompletedEventArgs { Cookies = cookies, Url = uri.AbsolutePath, RelativeUrl = uri.PathAndQuery, Host = uri.Host };
handler?.Invoke(this, onPageCompletedEventArgs);
}
private void AllowCookies(WebView view)
{
CookieManager.Instance.Flush();
CookieManager.AllowFileSchemeCookies();
CookieManager.SetAcceptFileSchemeCookies(true);
CookieManager.Instance.AcceptCookie();
CookieManager.Instance.AcceptThirdPartyCookies(view);
CookieManager.Instance.SetAcceptCookie(true);
CookieManager.Instance.SetAcceptThirdPartyCookies(view, true);
}
The problem is, I am able to get just one cookie(wc_cookie_ps_ck
), I am unable to see the other authentication cookie(.AspNetCore.Cookies
).
Here's how the cookies appear in browser.
Please note that in postman and in chrome browser both the cookies appear.
But in android webview only cookie with name ".AspNetCore.Cookies" is not appearing at all.
As per Java document,"When retrieving cookies from the cookie store, CookieManager also enforces the path-match rule from section 3.3.4 of RFC 2965 . So, a cookie must also have its “path” attribute set so that the path-match rule can be applied before the cookie is retrieved from the cookie store."
Since both of my cookies have different path, is that the reason the one with path set as "/project" is not appearing?
After days and days of finding the answer to the question. I finally have found an answer.
I did remote debugging of the webview with the desktop chrome and I found out that all the cookies that I needed were present in the webview.
However the method,
var cookies = CookieManager.Instance.GetCookie(url);
doesn't return the cookie which has the same site variable set.
This looks like a bug from Xamarin Android. I have already raised an issue in Xamarin Android github.
In the xamarin android github issue I have mentioned the steps to reproduce.
For me, the workaround to resolve the issue was to set the samesite cookie varibale off in my asp.net core back end project.
As follows:
In order to configure the application cookie when using Identity, you can use the ConfigureApplicationCookie method inside your Startup’s ConfigureServices:
// add identity
services.AddIdentity<ApplicationUser, IdentityRole>();
// configure the application cookie
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
Link for the above solution mentioned. Here.
I have an application with dynamic links implemented. I'm making request to get short link with all socialMetaTagInfo defined (socialTitle, socialDescription, socialImageLink)
link is so far only dummy page with simple and whole request looks like this (I'm not filling more fields):
{
"dynamicLinkInfo": {
"dynamicLinkDomain": "dl_domain",
"link": "http://www.my_page.com",
"androidInfo": {
"androidPackageName": string
},
"socialMetaTagInfo": {
"socialTitle": string,
"socialDescription": string,
"socialImageLink": string
}
}
}
When I share result short link on Facebook I can see nice post with image, title and description. However when I share the link via Google + only link is visible without social fields. (Same for Slack for example).
Do I have to create my fallback page first? Or am I doing something wrong? I just want to know how to display the image, title and description in other social apps (pages).
Title,Desc,Images are the additional meta tags which is added to your Dynamic links.
Apps like Facebook, Twitter, Google+ will be identifying the meta data from your url and load it in the UI.
It may fail to load if the Image size is large or if Google+ app is not coded to handle it.
Background: I have a working application which populates webview data by reading the response from an HttpURLConnection, running some processing on the markup, and then feeding the complete string to WebView.loadDataWithBaseUrl to display the page. This approach works, but has the performance drawback of eliminating concurrent processing of the partially loaded document which happens by default when loading a page normally in any browser or webview.
Goal: I'd like to change my application to a streaming model instead of a two-step serial process. In short, I'm investigating whether I can move from this:
Open the response stream from the server.
Block and read everything from the server until end of document.
Hand the whole thing to the webview at once.
to this:
Open the response stream from the server.
Read any available data and hand it immediately to the webview.
Repeat 2 until end of document.
For an additional wrinkle, the webview pages use both http:// and file:///android_asset requests, which work fine in my current setup. My main difficulty preserving access to both of these. Sidenote: I'm supporting API v14+.
Here's a simplified code summary of my current attempt.
// -- Setup --
WebView webview = makeWebView(); // WebSettings, JS on, view setup, etc
String url = "http://mywebsite.com";
InputStream is = getResponseStreamFromNetwork(url);
// -- Current data injection code --
String completeMarkup = readEverythingAtOnce(is);
webview.loadDataWithBaseUrl(url, completeMarkup, "text/html", "UTF-8", url);
// -- Ideal future data injection code --
webview.loadDataWithBaseUrl(url, "", "text/html", "UTF-8", url);
String line = null;
while ((line = is.readLine()) != null) {
appendToWebView(webview, line);
}
finishWebView(webview);
// -- The hard bits --
void appendToWebView(WebView webview, String line) {
// Inject a partial markup string to the end of the webview's content.
webview.loadUrl("javascript:document.write('" + line.replace("'", "\\'") + "\\n');");
}
void finishWebView(WebView webview) {
// We're done injecting data. Tell the webview we're finished if needed.
webview.loadUrl("javascript:document.write('\\n\\n');");
webview.loadUrl("javascript:document.close();");
}
As written, this successfully streams markup to the webview via document.write, and the page is properly displayed, but my file:///android_asset data requests all get denied access. Changing the base URL of loadDataWithBaseUrl to file:///android_asset fixes those but breaks access to http:// requests via XmlHttpRequest. I assume that this is by-design same origin policy specifically for content introduced via document.write. If that's the case, is there an alternative method for injecting this content piece by piece?
In short, is there a way to reimplement appendToWebView and finishWebView or change something elsewhere to stream data into the webview while avoiding this access problem?
So yes, mixing http(s) and file:// scheme is going to break due to the same origin policy.
This should work instead:
host your entire contents under a 'virtual' http(s) URL (ideally one that you own, or use androidplatform.net),
use WebViewClient.shouldInterceptRequest to intercept the requests to the 'virtual' URL and provide either the network InputStream or an InputStream associated with the asset.
Edit: The relevant bit of code the WebView uses to handle file://android_asset URLs is here.
I have an app that heavily uses the Android WebView to display my custom HTML content. The latest Android update (4.4/Kit-Kat/SDK-19) featured a redesigned WebView.
One of my users with a Nexus 5 reported a problem where some links cause the app to crash. I ran in the 4.4 emulator and debug into my WebViewClient's shouldOverrideUrlLoading() method. On all previously tested Android versions (2.2-4.3) the url String passed into the method had my custom url with "/" characters in it. In 4.4 the exact same link now has "\" characters in their place.
This doesn't make any sense to me. I load the HTML exactly the same, so somehow the new WebView converted all my slashes into backslashes.
Why does the new WebView do this?
Changes in URL handling are a known issue. Please see the migration guide for more detail.
The behaviour in this particular case will depend on what your base URL's scheme is, from what you're describing I'm guessing your base URL's scheme is "http(s)://" in which case the Chromium WebView performs URL normalization.
You might want to consider using the URI class to handle the discrepancy between the Classic and Chromium WebViews in this case.
I did more debugging and discovered I actually have the question reversed. Turns out the older versions of WebView did conversions of the URL, not the new one.
I load HTML with a format similar to this into a WebView:
link
I use the double back slashes as delimiters and parse the data later when the link is clicked. In older versions of WebView it converted my double backslash characters into forward slashes. It had been so long since I was in that code, I forgot I adjusted my code to use forward slashes rather than the backslashes in the original HTML.
The new version of WebView leaves my custom URL intact, giving me the exact same string as my original HTML. So turns out the old WebView is the problem not the new one.
The new WebView applies additional restrictions when requesting resources and resolving links that use a custom URL scheme. For example, if you implement callbacks such as shouldOverrideUrlLoading() or shouldInterceptRequest(), then WebView invokes them only for valid URLs.
If you are using a custom URL scheme or a base URL and notice that your app is receiving fewer calls to these callbacks or failing to load resources on Android 4.4, ensure that the requests specify valid URLs that conform to RFC 3986.
For example, the new WebView may not call your shouldOverrideUrlLoading() method for links like this:
Show Profile
The result of the user clicking such a link can vary:
If you loaded the page by calling loadData() or loadDataWithBaseURL() with an invalid or null base URL, then you will not receive the shouldOverrideUrlLoading() callback for this type of link on the page.
Note: When you use loadDataWithBaseURL() and the base URL is invalid or set null, all links in the content you are loading must be absolute.
If you loaded the page by calling loadUrl() or provided a valid base URL with loadDataWithBaseURL(), then you will receive the shouldOverrideUrlLoading() callback for this type of link on the page, but the URL you receive will be absolute, relative to the current page. For example, the URL you receive will be "http://www.example.com/showProfile" instead of just "showProfile".
Instead of using a simple string in a link as shown above, you can use a custom scheme such as the following:
Show Profile
You can then handle this URL in your shouldOverrideUrlLoading() method like this:
// The URL scheme should be non-hierarchical (no trailing slashes)
private static final String APP_SCHEME = "example-app:";
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(APP_SCHEME)) {
urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
respondToData(urlData);
return true;
}
return false;
}
If you can't alter the HTML then you may be able to use loadDataWithBaseURL() and set a base URL consisting of a custom scheme and a valid host, such as "example-app:///". For example:
webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
null, "UTF-8", null);
The valid host name should conform to RFC 3986 and it's important to include the trailing slash at the end, otherwise, any requests from the loaded page may be dropped.
to avoid webview below 4.4 convert backslash to forward slash, I just escape my url, then in Java code, use URI.decode to get the real url.That works for me.
Having trouble downloading an image when using the Android Query (aQuery) library to download image that has redirect:
Using url https://api.twitter.com/1/users/profile_image?screen_name=scottyab&size=bigger
in web browser redirects to
https://si0.twimg.com/profile_images/2467461312/5rab7gapdrnd84b6nflx_bigger.jpeg
Here's the activity code for doing the async image call:
aq.id(R.id.twitter_image)
.progress(R.id.twitter_image_progress)
.image(getTwitterProfileUrl(twitterName),
true, true, 0, R.drawable.no_profile_placeholder,
new MyBitmapCallback());
Getting an internal AQuery error 101 network error. This there an option to enable AQuery redirect? The URL returned in the call back is the original url not the redirected URL. Specifically finding this an issue on Android 2.1
Android-Query doesn't appear to handle redirects currently, and it looks like it would require a code change to support them.
I took a look at the AQuery source code, and I think this is the code to do the request: https://code.google.com/p/android-query/source/browse/trunk/src/com/androidquery/callback/AbstractAjaxCallback.java#1483
Here is how it could be implemented to follow redirects:
Httpclient redirect handler
It would have to handle codes >= 300 and < 400 to handle redirects.