Skip to content

Instantly share code, notes, and snippets.

@alexcohn
Created February 25, 2021 07:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexcohn/c1f756e8dd95f7eb1251c8bd9ccd8ea8 to your computer and use it in GitHub Desktop.
Save alexcohn/c1f756e8dd95f7eb1251c8bd9ccd8ea8 to your computer and use it in GitHub Desktop.
WebView.postUrl with additionalHttpHeaders via extension
fun WebView.postUrl(url: String, postData: ByteArray, additionalHttpHeaders: MutableMap<String, String>) {
val savedWebViewClient = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
webViewClient
}
else {
getLegacyWebViewClient()
}
webViewClient = object : WebViewClient() {
@Suppress("DEPRECATION")
override fun shouldInterceptRequest(view: WebView, requestUrl: String): WebResourceResponse? {
if (requestUrl != url) {
if (savedWebViewClient != null) {
webViewClient = savedWebViewClient
}
return savedWebViewClient?.shouldInterceptRequest(view, requestUrl)
}
Log.d("WebViewClient", "(url) post \"${postData.decodeToString()}\" to ${requestUrl}")
val httpsUrl = URL(requestUrl)
val conn: HttpsURLConnection = httpsUrl.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
for ((key, value) in additionalHttpHeaders) {
conn.addRequestProperty(key, value)
}
val os = conn.outputStream
os.write(postData)
os.close()
val responseCode = conn.responseCode
Log.d("WebViewClient", "responseCode = ${responseCode} ${conn.contentType}")
try {
val response = conn.inputStream.readBytes().decodeToString()
if (savedWebViewClient != null) {
webViewClient = savedWebViewClient
}
view.post {
view.loadData(response, conn.contentType, conn.contentEncoding)
}
}
catch (ex: Exception) {
return null
}
return WebResourceResponse(null, null, null)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
val requestUrl = request.url!!.toString()
if (!request.isForMainFrame || request.method != "POST" || requestUrl != url) {
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
}
return savedWebViewClient?.shouldInterceptRequest(view, request)
}
Log.d("WebViewClient", "(request) ${request.method} \"${postData.decodeToString()}\" to ${requestUrl}")
val httpsUrl = URL(requestUrl)
val conn: HttpsURLConnection = httpsUrl.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
for ((key, value) in request.requestHeaders) {
conn.addRequestProperty(key, value)
}
for ((key, value) in additionalHttpHeaders) {
conn.addRequestProperty(key, value)
}
val os = conn.outputStream
os.write(postData)
os.close()
val responseCode = conn.responseCode
Log.d("WebViewClient", "responseCode=${responseCode} ${conn.contentType}")
view.post {
if (savedWebViewClient != null) {
webViewClient = savedWebViewClient
}
}
val responseHeaders = mutableMapOf<String, String>()
for ((key, list) in conn.headerFields) {
if (key != null && list != null && list.size >= 1) {
responseHeaders[key] = list.get(0)
}
}
// typical conn.contentType is "text/html; charset=UTF-8"; conn.contentEncoding is less reliable
return WebResourceResponse(
conn.contentType.substringBefore(";"),
conn.contentType.substringAfter("charset=", "UTF-8"),
conn.responseCode,
conn.responseMessage,
responseHeaders,
conn.inputStream)
}
@Suppress("DEPRECATION")
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
if (savedWebViewClient == null) {
return false
}
return savedWebViewClient.shouldOverrideUrlLoading(view, url)
}
@RequiresApi(Build.VERSION_CODES.N)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
if (savedWebViewClient == null) {
return false
}
return savedWebViewClient.shouldOverrideUrlLoading(view, request)
}
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onPageStarted(view, url, favicon)
}
}
override fun onPageFinished(view: WebView, url: String?) {
Log.w("WebViewClient", "unexpected: onPageFinished ${url}")
}
override fun onLoadResource(view: WebView, url: String?) {
Log.w("WebViewClient", "unexpected: onLoadResource ${url}")
}
override fun onPageCommitVisible(view: WebView, url: String?) {
Log.w("WebViewClient", "unexpected: onPageCommitVisible ${url}")
}
override fun onTooManyRedirects(view: WebView, cancelMsg: Message, continueMsg: Message?) {
Log.w("WebViewClient", "unexpected: onTooManyRedirects ${url}")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onPageStarted(view, url, favicon)
}
else {
super.onTooManyRedirects(view, cancelMsg, continueMsg)
}
}
@Suppress("DEPRECATION")
override fun onReceivedError(view: WebView, errorCode: Int, description: String?, failingUrl: String?) {
Log.i("WebViewClient", "onReceivedError ${url}")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onReceivedError(view, errorCode, description, failingUrl)
}
else {
super.onReceivedError(view, errorCode, description, failingUrl)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
Log.i("WebViewClient", "onReceivedError ${request.url}")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onReceivedError(view, request, error)
}
else {
super.onReceivedError(view, request, error)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse?) {
Log.i("WebViewClient", "onReceivedHttpError ${request.url}")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onReceivedHttpError(view, request, errorResponse)
}
else {
super.onReceivedHttpError(view, request, errorResponse)
}
}
override fun onFormResubmission(view: WebView, dontResend: Message, resend: Message?) {
Log.i("WebViewClient", "onFormResubmission")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.onFormResubmission(view, dontResend, resend)
}
else {
super.onFormResubmission(view, dontResend, resend)
}
}
override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) {
Log.i("WebViewClient", "doUpdateVisitedHistory")
if (savedWebViewClient != null) {
view.post {
webViewClient = savedWebViewClient
}
savedWebViewClient.doUpdateVisitedHistory(view, url, isReload)
}
else {
super.doUpdateVisitedHistory(view, url, isReload)
}
}
}
postUrl(url, postData)
}
fun WebView.getLegacyWebViewClient(): WebViewClient? {
try {
val mProviderField = javaClass.getDeclaredField("mProvider")
mProviderField.isAccessible = true
val mProvider = mProviderField.get(this)
if (mProvider != null) {
val mContentsClientAdapterField = mProvider.javaClass.getDeclaredField("mContentsClientAdapter")
mContentsClientAdapterField.isAccessible = true
val mContentsClientAdapter = mContentsClientAdapterField.get(mProvider)
if (mContentsClientAdapter != null) {
val mWebViewClientField = mContentsClientAdapter.javaClass.getDeclaredField("mWebViewClient")
mWebViewClientField.isAccessible = true
val mWebViewClient = mWebViewClientField.get(mContentsClientAdapter)
Log.i("WebViewClient", "getLegacyWebViewClient ${mWebViewClient}")
return mWebViewClient as WebViewClient
}
}
Log.i("WebViewClient", "getLegacyWebViewClient null")
}
catch (ex: Throwable) {
Log.w("WebViewClient", "getLegacyWebViewClient failed", ex)
}
return null
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment