Created
February 26, 2019 07:29
-
-
Save samigehi/e431aa6696a87df8c84cfebcc92aa677 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.allure.mht; | |
/* | |
* Licensed under the MIT License (https://opensource.org/licenses/MIT) | |
*/ | |
import android.annotation.SuppressLint; | |
import android.app.Activity; | |
import android.app.DownloadManager; | |
import android.app.DownloadManager.Request; | |
import android.app.Fragment; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.PackageManager; | |
import android.graphics.Bitmap; | |
import android.net.Uri; | |
import android.net.http.SslError; | |
import android.os.Build; | |
import android.os.Environment; | |
import android.os.Message; | |
import android.util.AttributeSet; | |
import android.util.Base64; | |
import android.util.Log; | |
import android.view.KeyEvent; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.webkit.*; | |
import android.webkit.GeolocationPermissions.Callback; | |
import android.webkit.WebStorage.QuotaUpdater; | |
import java.io.UnsupportedEncodingException; | |
import java.lang.ref.WeakReference; | |
import java.util.*; | |
//import com.github.loadingview.LoadingView; | |
/** | |
* Advanced WebView component for Android that works as intended out of the box | |
*/ | |
@SuppressWarnings("deprecation") | |
public class AdvancedWebView extends WebView { | |
public interface Listener { | |
void onPageStarted(String url, Bitmap favicon); | |
void onPageFinished(String url); | |
void onPageError(int errorCode, String description, String failingUrl); | |
void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent); | |
void onExternalPageRequest(String url); | |
} | |
public static final String PACKAGE_NAME_DOWNLOAD_MANAGER = "com.android.providers.downloads"; | |
protected static final int REQUEST_CODE_FILE_PICKER = 51426; | |
protected static final String DATABASES_SUB_FOLDER = "/databases"; | |
protected static final String LANGUAGE_DEFAULT_ISO3 = "eng"; | |
protected static final String CHARSET_DEFAULT = "UTF-8"; | |
/** | |
* Alternative browsers that have their own rendering engine and *may* be installed on this device | |
*/ | |
protected static final String[] ALTERNATIVE_BROWSERS = new String[]{"org.mozilla.firefox", "com.android.chrome", "com.opera.browser", "org.mozilla.firefox_beta", "com.chrome.beta", "com.opera.browser.beta"}; | |
protected WeakReference<Activity> mActivity; | |
protected WeakReference<Fragment> mFragment; | |
protected Listener mListener; | |
protected final List<String> mPermittedHostnames = new LinkedList<String>(); | |
/** | |
* File upload callback for platform versions prior to Android 5.0 | |
*/ | |
protected ValueCallback<Uri> mFileUploadCallbackFirst; | |
/** | |
* File upload callback for Android 5.0+ | |
*/ | |
protected ValueCallback<Uri[]> mFileUploadCallbackSecond; | |
protected long mLastError; | |
protected String mLanguageIso3; | |
protected int mRequestCodeFilePicker = REQUEST_CODE_FILE_PICKER; | |
protected WebViewClient mCustomWebViewClient; | |
protected WebChromeClient mCustomWebChromeClient; | |
protected boolean mGeolocationEnabled; | |
protected String mUploadableFileTypes = "*/*"; | |
protected final Map<String, String> mHttpHeaders = new HashMap<String, String>(); | |
public AdvancedWebView(Context context) { | |
super(context); | |
init(context); | |
} | |
public AdvancedWebView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context); | |
} | |
public AdvancedWebView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(context); | |
} | |
public void setListener(final Activity activity, final Listener listener) { | |
setListener(activity, listener, REQUEST_CODE_FILE_PICKER); | |
} | |
public void setListener(final Activity activity, final Listener listener, final int requestCodeFilePicker) { | |
if (activity != null) { | |
mActivity = new WeakReference<Activity>(activity); | |
} else { | |
mActivity = null; | |
} | |
setListener(listener, requestCodeFilePicker); | |
} | |
public void setListener(final Fragment fragment, final Listener listener) { | |
setListener(fragment, listener, REQUEST_CODE_FILE_PICKER); | |
} | |
public void setListener(final Fragment fragment, final Listener listener, final int requestCodeFilePicker) { | |
if (fragment != null) { | |
mFragment = new WeakReference<Fragment>(fragment); | |
} else { | |
mFragment = null; | |
} | |
setListener(listener, requestCodeFilePicker); | |
} | |
protected void setListener(final Listener listener, final int requestCodeFilePicker) { | |
mListener = listener; | |
mRequestCodeFilePicker = requestCodeFilePicker; | |
} | |
@Override | |
public void setWebViewClient(final WebViewClient client) { | |
mCustomWebViewClient = client; | |
} | |
@Override | |
public void setWebChromeClient(final WebChromeClient client) { | |
mCustomWebChromeClient = client; | |
} | |
@SuppressLint("SetJavaScriptEnabled") | |
public void setGeolocationEnabled(final boolean enabled) { | |
if (enabled) { | |
getSettings().setJavaScriptEnabled(true); | |
getSettings().setGeolocationEnabled(true); | |
setGeolocationDatabasePath(); | |
} | |
mGeolocationEnabled = enabled; | |
} | |
@SuppressLint("NewApi") | |
protected void setGeolocationDatabasePath() { | |
final Activity activity; | |
if (mFragment != null && mFragment.get() != null && Build.VERSION.SDK_INT >= 11 && mFragment.get().getActivity() != null) { | |
activity = mFragment.get().getActivity(); | |
} else if (mActivity != null && mActivity.get() != null) { | |
activity = mActivity.get(); | |
} else { | |
return; | |
} | |
getSettings().setGeolocationDatabasePath(activity.getFilesDir().getPath()); | |
} | |
public void setUploadableFileTypes(final String mimeType) { | |
mUploadableFileTypes = mimeType; | |
} | |
/** | |
* Loads and displays the provided HTML source text | |
* | |
* @param html the HTML source text to load | |
*/ | |
public void loadHtml(final String html) { | |
loadHtml(html, null); | |
} | |
/** | |
* Loads and displays the provided HTML source text | |
* | |
* @param html the HTML source text to load | |
* @param baseUrl the URL to use as the page's base URL | |
*/ | |
public void loadHtml(final String html, final String baseUrl) { | |
loadHtml(html, baseUrl, null); | |
} | |
/** | |
* Loads and displays the provided HTML source text | |
* | |
* @param html the HTML source text to load | |
* @param baseUrl the URL to use as the page's base URL | |
* @param historyUrl the URL to use for the page's history entry | |
*/ | |
public void loadHtml(final String html, final String baseUrl, final String historyUrl) { | |
loadHtml(html, baseUrl, historyUrl, "utf-8"); | |
} | |
/** | |
* Loads and displays the provided HTML source text | |
* | |
* @param html the HTML source text to load | |
* @param baseUrl the URL to use as the page's base URL | |
* @param historyUrl the URL to use for the page's history entry | |
* @param encoding the encoding or charset of the HTML source text | |
*/ | |
public void loadHtml(final String html, final String baseUrl, final String historyUrl, final String encoding) { | |
loadDataWithBaseURL(baseUrl, html, "text/html", encoding, historyUrl); | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onResume() { | |
if (Build.VERSION.SDK_INT >= 11) { | |
super.onResume(); | |
} | |
resumeTimers(); | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onPause() { | |
pauseTimers(); | |
if (Build.VERSION.SDK_INT >= 11) { | |
super.onPause(); | |
} | |
} | |
public void onDestroy() { | |
// try to remove this view from its parent first | |
try { | |
((ViewGroup) getParent()).removeView(this); | |
} catch (Exception ignored) { | |
} | |
// then try to remove all child views from this view | |
try { | |
removeAllViews(); | |
} catch (Exception ignored) { | |
} | |
// and finally destroy this view | |
destroy(); | |
} | |
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { | |
if (requestCode == mRequestCodeFilePicker) { | |
if (resultCode == Activity.RESULT_OK) { | |
if (intent != null) { | |
if (mFileUploadCallbackFirst != null) { | |
mFileUploadCallbackFirst.onReceiveValue(intent.getData()); | |
mFileUploadCallbackFirst = null; | |
} else if (mFileUploadCallbackSecond != null) { | |
Uri[] dataUris = null; | |
try { | |
if (intent.getDataString() != null) { | |
dataUris = new Uri[]{Uri.parse(intent.getDataString())}; | |
} else { | |
if (Build.VERSION.SDK_INT >= 16) { | |
if (intent.getClipData() != null) { | |
final int numSelectedFiles = intent.getClipData().getItemCount(); | |
dataUris = new Uri[numSelectedFiles]; | |
for (int i = 0; i < numSelectedFiles; i++) { | |
dataUris[i] = intent.getClipData().getItemAt(i).getUri(); | |
} | |
} | |
} | |
} | |
} catch (Exception ignored) { | |
} | |
mFileUploadCallbackSecond.onReceiveValue(dataUris); | |
mFileUploadCallbackSecond = null; | |
} | |
} | |
} else { | |
if (mFileUploadCallbackFirst != null) { | |
mFileUploadCallbackFirst.onReceiveValue(null); | |
mFileUploadCallbackFirst = null; | |
} else if (mFileUploadCallbackSecond != null) { | |
mFileUploadCallbackSecond.onReceiveValue(null); | |
mFileUploadCallbackSecond = null; | |
} | |
} | |
} | |
} | |
/** | |
* Adds an additional HTTP header that will be sent along with every HTTP `GET` request | |
* <p> | |
* This does only affect the main requests, not the requests to included resources (e.g. images) | |
* <p> | |
* If you later want to delete an HTTP header that was previously added this way, call `removeHttpHeader()` | |
* <p> | |
* The `WebView` implementation may in some cases overwrite headers that you set or unset | |
* | |
* @param name the name of the HTTP header to add | |
* @param value the value of the HTTP header to send | |
*/ | |
public void addHttpHeader(final String name, final String value) { | |
mHttpHeaders.put(name, value); | |
} | |
/** | |
* Removes one of the HTTP headers that have previously been added via `addHttpHeader()` | |
* <p> | |
* If you want to unset a pre-defined header, set it to an empty string with `addHttpHeader()` instead | |
* <p> | |
* The `WebView` implementation may in some cases overwrite headers that you set or unset | |
* | |
* @param name the name of the HTTP header to remove | |
*/ | |
public void removeHttpHeader(final String name) { | |
mHttpHeaders.remove(name); | |
} | |
public void addPermittedHostname(String hostname) { | |
mPermittedHostnames.add(hostname); | |
} | |
public void addPermittedHostnames(Collection<? extends String> collection) { | |
mPermittedHostnames.addAll(collection); | |
} | |
public List<String> getPermittedHostnames() { | |
return mPermittedHostnames; | |
} | |
public void removePermittedHostname(String hostname) { | |
mPermittedHostnames.remove(hostname); | |
} | |
public void clearPermittedHostnames() { | |
mPermittedHostnames.clear(); | |
} | |
public boolean onBackPressed() { | |
if (canGoBack()) { | |
goBack(); | |
return false; | |
} else { | |
return true; | |
} | |
} | |
@SuppressLint("NewApi") | |
protected static void setAllowAccessFromFileUrls(final WebSettings webSettings, final boolean allowed) { | |
if (Build.VERSION.SDK_INT >= 16) { | |
webSettings.setAllowFileAccessFromFileURLs(allowed); | |
webSettings.setAllowUniversalAccessFromFileURLs(allowed); | |
} | |
} | |
@SuppressWarnings("static-method") | |
public void setCookiesEnabled(final boolean enabled) { | |
CookieManager.getInstance().setAcceptCookie(enabled); | |
} | |
@SuppressLint("NewApi") | |
public void setThirdPartyCookiesEnabled(final boolean enabled) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
CookieManager.getInstance().setAcceptThirdPartyCookies(this, enabled); | |
} | |
} | |
public void setMixedContentAllowed(final boolean allowed) { | |
setMixedContentAllowed(getSettings(), allowed); | |
} | |
@SuppressWarnings("static-method") | |
@SuppressLint("NewApi") | |
protected void setMixedContentAllowed(final WebSettings webSettings, final boolean allowed) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
webSettings.setMixedContentMode(allowed ? WebSettings.MIXED_CONTENT_ALWAYS_ALLOW : WebSettings.MIXED_CONTENT_NEVER_ALLOW); | |
} | |
} | |
public void setDesktopMode(final boolean enabled) { | |
final WebSettings webSettings = getSettings(); | |
final String newUserAgent; | |
if (enabled) { | |
newUserAgent = webSettings.getUserAgentString().replace("Mobile", "eliboM").replace("Android", "diordnA"); | |
} else { | |
newUserAgent = webSettings.getUserAgentString().replace("eliboM", "Mobile").replace("diordnA", "Android"); | |
} | |
webSettings.setUserAgentString(newUserAgent); | |
webSettings.setUseWideViewPort(enabled); | |
webSettings.setLoadWithOverviewMode(enabled); | |
webSettings.setSupportZoom(enabled); | |
webSettings.setBuiltInZoomControls(enabled); | |
} | |
// TODO init | |
@SuppressLint({"SetJavaScriptEnabled"}) | |
protected void init(Context context) { | |
// in IDE's preview mode | |
if (isInEditMode()) { | |
// do not run the code from this method | |
return; | |
} | |
if (context instanceof Activity) { | |
mActivity = new WeakReference<Activity>((Activity) context); | |
} | |
mLanguageIso3 = getLanguageIso3(); | |
//LoadingView | |
setFocusable(true); | |
setFocusableInTouchMode(true); | |
setSaveEnabled(true); | |
final String filesDir = context.getFilesDir().getPath(); | |
final String databaseDir = filesDir.substring(0, filesDir.lastIndexOf("/")) + DATABASES_SUB_FOLDER; | |
final WebSettings webSettings = getSettings(); | |
webSettings.setAllowFileAccess(false); | |
setAllowAccessFromFileUrls(webSettings, false); | |
webSettings.setBuiltInZoomControls(false); | |
webSettings.setJavaScriptEnabled(true); | |
webSettings.setDomStorageEnabled(true); | |
webSettings.setUseWideViewPort(true); | |
webSettings.setAppCacheEnabled(true); | |
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); | |
//webSettings.setLoadWithOverviewMode(true); | |
if (Build.VERSION.SDK_INT < 18) { | |
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); | |
} | |
webSettings.setDatabaseEnabled(true); | |
if (Build.VERSION.SDK_INT < 19) { | |
webSettings.setDatabasePath(databaseDir); | |
} | |
setMixedContentAllowed(webSettings, true); | |
setThirdPartyCookiesEnabled(true); | |
super.setWebViewClient(new WebViewClient() { | |
@Override | |
public void onPageStarted(WebView view, String url, Bitmap favicon) { | |
if (!hasError()) { | |
if (mListener != null) { | |
mListener.onPageStarted(url, favicon); | |
} | |
} | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onPageStarted(view, url, favicon); | |
} | |
} | |
@Override | |
public void onPageFinished(WebView view, String url) { | |
if (!hasError()) { | |
if (mListener != null) { | |
mListener.onPageFinished(url); | |
} | |
} | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onPageFinished(view, url); | |
} | |
} | |
@Override | |
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { | |
setLastError(); | |
if (mListener != null) { | |
mListener.onPageError(errorCode, description, failingUrl); | |
} | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onReceivedError(view, errorCode, description, failingUrl); | |
} | |
} | |
@Override | |
public boolean shouldOverrideUrlLoading(final WebView view, final String url) { | |
log("shouldOverrideUrlLoading " + url); | |
if (!isPermittedUrl(url)) { | |
// if a listener is available | |
if (mListener != null) { | |
// inform the listener about the request | |
mListener.onExternalPageRequest(url); | |
} | |
log("shouldOverrideUrlLoading isPermittedUrl"); | |
// cancel the original request | |
return true; | |
} | |
// if there is a user-specified handler available | |
if (mCustomWebViewClient != null) { | |
// if the user-specified handler asks to override the request | |
if (mCustomWebViewClient.shouldOverrideUrlLoading(view, url)) { | |
// cancel the original request | |
log("shouldOverrideUrlLoading mCustomWebViewClient"); | |
return true; | |
} | |
} | |
if (url.startsWith("market:/") || url.startsWith("whatsapp:/")) { | |
log("shouldOverrideUrlLoading matched"); | |
if (mListener != null) { | |
mListener.onExternalPageRequest(url); | |
} | |
return true; | |
} | |
// route the request through the custom URL loading method | |
view.loadUrl(url); | |
// cancel the original request | |
return true; | |
} | |
@Override | |
public void onLoadResource(WebView view, String url) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onLoadResource(view, url); | |
} else { | |
super.onLoadResource(view, url); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { | |
if (Build.VERSION.SDK_INT >= 11) { | |
if (mCustomWebViewClient != null) { | |
return mCustomWebViewClient.shouldInterceptRequest(view, url); | |
} else { | |
return super.shouldInterceptRequest(view, url); | |
} | |
} else { | |
return null; | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
if (mCustomWebViewClient != null) { | |
return mCustomWebViewClient.shouldInterceptRequest(view, request); | |
} else { | |
return super.shouldInterceptRequest(view, request); | |
} | |
} else { | |
return null; | |
} | |
} | |
@Override | |
public void onFormResubmission(WebView view, Message dontResend, Message resend) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onFormResubmission(view, dontResend, resend); | |
} else { | |
super.onFormResubmission(view, dontResend, resend); | |
} | |
} | |
@Override | |
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.doUpdateVisitedHistory(view, url, isReload); | |
} else { | |
super.doUpdateVisitedHistory(view, url, isReload); | |
} | |
} | |
@Override | |
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onReceivedSslError(view, handler, error); | |
} else { | |
super.onReceivedSslError(view, handler, error); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onReceivedClientCertRequest(view, request); | |
} else { | |
super.onReceivedClientCertRequest(view, request); | |
} | |
} | |
} | |
@Override | |
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm); | |
} else { | |
super.onReceivedHttpAuthRequest(view, handler, host, realm); | |
} | |
} | |
@Override | |
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { | |
if (mCustomWebViewClient != null) { | |
return mCustomWebViewClient.shouldOverrideKeyEvent(view, event); | |
} else { | |
return super.shouldOverrideKeyEvent(view, event); | |
} | |
} | |
@Override | |
public void onUnhandledKeyEvent(WebView view, KeyEvent event) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onUnhandledKeyEvent(view, event); | |
} else { | |
super.onUnhandledKeyEvent(view, event); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
// public void onUnhandledInputEvent(WebView view, InputEvent event) { | |
// if (Build.VERSION.SDK_INT >= 21) { | |
// if (mCustomWebViewClient != null) { | |
// mCustomWebViewClient.onUnhandledKeyEvent(view, event); | |
// } | |
// else { | |
// super.onUnhandledInputEvent(view, event); | |
// } | |
// } | |
// } | |
@Override | |
public void onScaleChanged(WebView view, float oldScale, float newScale) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onScaleChanged(view, oldScale, newScale); | |
} else { | |
super.onScaleChanged(view, oldScale, newScale); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { | |
if (Build.VERSION.SDK_INT >= 12) { | |
if (mCustomWebViewClient != null) { | |
mCustomWebViewClient.onReceivedLoginRequest(view, realm, account, args); | |
} else { | |
super.onReceivedLoginRequest(view, realm, account, args); | |
} | |
} | |
} | |
}); | |
super.setWebChromeClient(new WebChromeClient() { | |
// file upload callback (Android 2.2 (API level 8) -- Android 2.3 (API level 10)) (hidden method) | |
@SuppressWarnings("unused") | |
public void openFileChooser(ValueCallback<Uri> uploadMsg) { | |
openFileChooser(uploadMsg, null); | |
} | |
// file upload callback (Android 3.0 (API level 11) -- Android 4.0 (API level 15)) (hidden method) | |
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { | |
openFileChooser(uploadMsg, acceptType, null); | |
} | |
// file upload callback (Android 4.1 (API level 16) -- Android 4.3 (API level 18)) (hidden method) | |
@SuppressWarnings("unused") | |
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { | |
openFileInput(uploadMsg, null, false); | |
} | |
// file upload callback (Android 5.0 (API level 21) -- current) (public method) | |
@SuppressWarnings("all") | |
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE; | |
openFileInput(null, filePathCallback, allowMultiple); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public void onProgressChanged(WebView view, int newProgress) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onProgressChanged(view, newProgress); | |
} else { | |
super.onProgressChanged(view, newProgress); | |
} | |
} | |
@Override | |
public void onReceivedTitle(WebView view, String title) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onReceivedTitle(view, title); | |
} else { | |
super.onReceivedTitle(view, title); | |
} | |
} | |
@Override | |
public void onReceivedIcon(WebView view, Bitmap icon) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onReceivedIcon(view, icon); | |
} else { | |
super.onReceivedIcon(view, icon); | |
} | |
} | |
@Override | |
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed); | |
} else { | |
super.onReceivedTouchIconUrl(view, url, precomposed); | |
} | |
} | |
@Override | |
public void onShowCustomView(View view, CustomViewCallback callback) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onShowCustomView(view, callback); | |
} else { | |
super.onShowCustomView(view, callback); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { | |
if (Build.VERSION.SDK_INT >= 14) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onShowCustomView(view, requestedOrientation, callback); | |
} else { | |
super.onShowCustomView(view, requestedOrientation, callback); | |
} | |
} | |
} | |
@Override | |
public void onHideCustomView() { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onHideCustomView(); | |
} else { | |
super.onHideCustomView(); | |
} | |
} | |
@Override | |
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg); | |
} else { | |
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); | |
} | |
} | |
@Override | |
public void onRequestFocus(WebView view) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onRequestFocus(view); | |
} else { | |
super.onRequestFocus(view); | |
} | |
} | |
@Override | |
public void onCloseWindow(WebView window) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onCloseWindow(window); | |
} else { | |
super.onCloseWindow(window); | |
} | |
} | |
@Override | |
public boolean onJsAlert(WebView view, String url, String message, JsResult result) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onJsAlert(view, url, message, result); | |
} else { | |
return super.onJsAlert(view, url, message, result); | |
} | |
} | |
@Override | |
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onJsConfirm(view, url, message, result); | |
} else { | |
return super.onJsConfirm(view, url, message, result); | |
} | |
} | |
@Override | |
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onJsPrompt(view, url, message, defaultValue, result); | |
} else { | |
return super.onJsPrompt(view, url, message, defaultValue, result); | |
} | |
} | |
@Override | |
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onJsBeforeUnload(view, url, message, result); | |
} else { | |
return super.onJsBeforeUnload(view, url, message, result); | |
} | |
} | |
@Override | |
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { | |
if (mGeolocationEnabled) { | |
callback.invoke(origin, true, false); | |
} else { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); | |
} else { | |
super.onGeolocationPermissionsShowPrompt(origin, callback); | |
} | |
} | |
} | |
@Override | |
public void onGeolocationPermissionsHidePrompt() { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onGeolocationPermissionsHidePrompt(); | |
} else { | |
super.onGeolocationPermissionsHidePrompt(); | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onPermissionRequest(PermissionRequest request) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onPermissionRequest(request); | |
} else { | |
super.onPermissionRequest(request); | |
} | |
} | |
} | |
@SuppressLint("NewApi") | |
@SuppressWarnings("all") | |
public void onPermissionRequestCanceled(PermissionRequest request) { | |
if (Build.VERSION.SDK_INT >= 21) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onPermissionRequestCanceled(request); | |
} else { | |
super.onPermissionRequestCanceled(request); | |
} | |
} | |
} | |
@Override | |
public boolean onJsTimeout() { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onJsTimeout(); | |
} else { | |
return super.onJsTimeout(); | |
} | |
} | |
@Override | |
public void onConsoleMessage(String message, int lineNumber, String sourceID) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onConsoleMessage(message, lineNumber, sourceID); | |
} else { | |
super.onConsoleMessage(message, lineNumber, sourceID); | |
} | |
} | |
@Override | |
public boolean onConsoleMessage(ConsoleMessage consoleMessage) { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.onConsoleMessage(consoleMessage); | |
} else { | |
return super.onConsoleMessage(consoleMessage); | |
} | |
} | |
@Override | |
public Bitmap getDefaultVideoPoster() { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.getDefaultVideoPoster(); | |
} else { | |
return super.getDefaultVideoPoster(); | |
} | |
} | |
@Override | |
public View getVideoLoadingProgressView() { | |
if (mCustomWebChromeClient != null) { | |
return mCustomWebChromeClient.getVideoLoadingProgressView(); | |
} else { | |
return super.getVideoLoadingProgressView(); | |
} | |
} | |
@Override | |
public void getVisitedHistory(ValueCallback<String[]> callback) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.getVisitedHistory(callback); | |
} else { | |
super.getVisitedHistory(callback); | |
} | |
} | |
@Override | |
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); | |
} else { | |
super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); | |
} | |
} | |
@Override | |
public void onReachedMaxAppCacheSize(long requiredStorage, long quota, QuotaUpdater quotaUpdater) { | |
if (mCustomWebChromeClient != null) { | |
mCustomWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); | |
} else { | |
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); | |
} | |
} | |
}); | |
setDownloadListener(new DownloadListener() { | |
@Override | |
public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimeType, final long contentLength) { | |
final String suggestedFilename = URLUtil.guessFileName(url, contentDisposition, mimeType); | |
if (mListener != null) { | |
mListener.onDownloadRequested(url, suggestedFilename, mimeType, contentLength, contentDisposition, userAgent); | |
} | |
} | |
}); | |
} | |
@Override | |
public void loadUrl(final String url, Map<String, String> additionalHttpHeaders) { | |
if (additionalHttpHeaders == null) { | |
additionalHttpHeaders = mHttpHeaders; | |
} else if (mHttpHeaders.size() > 0) { | |
additionalHttpHeaders.putAll(mHttpHeaders); | |
} | |
super.loadUrl(url, additionalHttpHeaders); | |
} | |
@Override | |
public void loadUrl(final String url) { | |
if (mHttpHeaders.size() > 0) { | |
super.loadUrl(url, mHttpHeaders); | |
} else { | |
super.loadUrl(url); | |
} | |
} | |
public void loadUrl(String url, final boolean preventCaching) { | |
if (preventCaching) { | |
url = makeUrlUnique(url); | |
} | |
loadUrl(url); | |
} | |
public void loadUrl(String url, final boolean preventCaching, final Map<String, String> additionalHttpHeaders) { | |
if (preventCaching) { | |
url = makeUrlUnique(url); | |
} | |
loadUrl(url, additionalHttpHeaders); | |
} | |
protected static String makeUrlUnique(final String url) { | |
StringBuilder unique = new StringBuilder(); | |
unique.append(url); | |
if (url.contains("?")) { | |
unique.append('&'); | |
} else { | |
if (url.lastIndexOf('/') <= 7) { | |
unique.append('/'); | |
} | |
unique.append('?'); | |
} | |
unique.append(System.currentTimeMillis()); | |
unique.append('='); | |
unique.append(1); | |
return unique.toString(); | |
} | |
public boolean isPermittedUrl(final String url) { | |
// if the permitted hostnames have not been restricted to a specific set | |
try { | |
final String actualHost = Uri.parse(url).getHost(); | |
// if the hostname could not be determined, usually because the URL has been invalid | |
if (actualHost == null) { | |
return false; | |
} | |
if (mPermittedHostnames.size() == 0) { | |
// all hostnames are allowed | |
return true; | |
} | |
// get the actual hostname of the URL that is to be checked | |
// for every hostname in the set of permitted hosts | |
for (String expectedHost : mPermittedHostnames) { | |
// if the two hostnames match or if the actual host is a subdomain of the expected host | |
if (actualHost.equals(expectedHost) || actualHost.endsWith("." + expectedHost)) { | |
// the actual hostname of the URL to be checked is allowed | |
return true; | |
} | |
} | |
} catch (Exception ignored) { | |
} | |
// the actual hostname of the URL to be checked is not allowed since there were no matches | |
return false; | |
} | |
/** | |
* @deprecated use `isPermittedUrl` instead | |
*/ | |
protected boolean isHostnameAllowed(final String url) { | |
return isPermittedUrl(url); | |
} | |
protected void setLastError() { | |
mLastError = System.currentTimeMillis(); | |
} | |
protected boolean hasError() { | |
return (mLastError + 500) >= System.currentTimeMillis(); | |
} | |
protected static String getLanguageIso3() { | |
try { | |
return Locale.getDefault().getISO3Language().toLowerCase(Locale.US); | |
} catch (MissingResourceException e) { | |
return LANGUAGE_DEFAULT_ISO3; | |
} | |
} | |
/** | |
* Provides localizations for the 25 most widely spoken languages that have a ISO 639-2/T code | |
* | |
* @return the label for the file upload prompts as a string | |
*/ | |
protected String getFileUploadPromptLabel() { | |
try { | |
if (mLanguageIso3.equals("zho")) return decodeBase64("6YCJ5oup5LiA5Liq5paH5Lu2"); | |
else if (mLanguageIso3.equals("spa")) return decodeBase64("RWxpamEgdW4gYXJjaGl2bw=="); | |
else if (mLanguageIso3.equals("hin")) | |
return decodeBase64("4KSP4KSVIOCkq+CkvOCkvuCkh+CksiDgpJrgpYHgpKjgpYfgpII="); | |
else if (mLanguageIso3.equals("ben")) | |
return decodeBase64("4KaP4KaV4Kaf4Ka/IOCmq+CmvuCmh+CmsiDgpqjgpr/gprDgp43gpqzgpr7gpprgpqg="); | |
else if (mLanguageIso3.equals("ara")) return decodeBase64("2KfYrtiq2YrYp9ixINmF2YTZgSDZiNin2K3Yrw=="); | |
else if (mLanguageIso3.equals("por")) return decodeBase64("RXNjb2xoYSB1bSBhcnF1aXZv"); | |
else if (mLanguageIso3.equals("rus")) | |
return decodeBase64("0JLRi9Cx0LXRgNC40YLQtSDQvtC00LjQvSDRhNCw0LnQuw=="); | |
else if (mLanguageIso3.equals("jpn")) | |
return decodeBase64("MeODleOCoeOCpOODq+OCkumBuOaKnuOBl+OBpuOBj+OBoOOBleOBhA=="); | |
else if (mLanguageIso3.equals("pan")) | |
return decodeBase64("4KiH4Kmx4KiVIOCoq+CovuCoh+CosiDgqJrgqYHgqKPgqYs="); | |
else if (mLanguageIso3.equals("deu")) return decodeBase64("V8OkaGxlIGVpbmUgRGF0ZWk="); | |
else if (mLanguageIso3.equals("jav")) return decodeBase64("UGlsaWggc2lqaSBiZXJrYXM="); | |
else if (mLanguageIso3.equals("msa")) return decodeBase64("UGlsaWggc2F0dSBmYWls"); | |
else if (mLanguageIso3.equals("tel")) | |
return decodeBase64("4LCS4LCVIOCwq+CxhuCxluCwsuCxjeCwqOCxgSDgsI7gsILgsJrgsYHgsJXgsYvgsILgsKHgsL8="); | |
else if (mLanguageIso3.equals("vie")) return decodeBase64("Q2jhu41uIG3hu5l0IHThuq1wIHRpbg=="); | |
else if (mLanguageIso3.equals("kor")) return decodeBase64("7ZWY64KY7J2YIO2MjOydvOydhCDshKDtg50="); | |
else if (mLanguageIso3.equals("fra")) return decodeBase64("Q2hvaXNpc3NleiB1biBmaWNoaWVy"); | |
else if (mLanguageIso3.equals("mar")) return decodeBase64("4KSr4KS+4KSH4KSyIOCkqOCkv+CkteCkoeCkvg=="); | |
else if (mLanguageIso3.equals("tam")) | |
return decodeBase64("4K6S4K6w4K+BIOCuleCvh+CuvuCuquCvjeCuquCviCDgrqTgr4fgrrDgr43grrXgr4E="); | |
else if (mLanguageIso3.equals("urd")) | |
return decodeBase64("2KfbjNqpINmB2KfYptmEINmF24zauiDYs9uSINin2YbYqtiu2KfYqCDaqdix24zaug=="); | |
else if (mLanguageIso3.equals("fas")) | |
return decodeBase64("2LHYpyDYp9mG2KrYrtin2Kgg2qnZhtuM2K8g24zaqSDZgdin24zZhA=="); | |
else if (mLanguageIso3.equals("tur")) return decodeBase64("QmlyIGRvc3lhIHNlw6dpbg=="); | |
else if (mLanguageIso3.equals("ita")) return decodeBase64("U2NlZ2xpIHVuIGZpbGU="); | |
else if (mLanguageIso3.equals("tha")) | |
return decodeBase64("4LmA4Lil4Li34Lit4LiB4LmE4Lif4Lil4LmM4Lir4LiZ4Li24LmI4LiH"); | |
else if (mLanguageIso3.equals("guj")) | |
return decodeBase64("4KqP4KqVIOCqq+CqvuCqh+CqsuCqqOCrhyDgqqrgqrjgqoLgqqY="); | |
} catch (Exception ignored) { | |
} | |
// return English translation by default | |
return "Choose a file"; | |
} | |
protected static String decodeBase64(final String base64) throws IllegalArgumentException, UnsupportedEncodingException { | |
final byte[] bytes = Base64.decode(base64, Base64.DEFAULT); | |
return new String(bytes, CHARSET_DEFAULT); | |
} | |
@SuppressLint("NewApi") | |
protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) { | |
if (mFileUploadCallbackFirst != null) { | |
mFileUploadCallbackFirst.onReceiveValue(null); | |
} | |
mFileUploadCallbackFirst = fileUploadCallbackFirst; | |
if (mFileUploadCallbackSecond != null) { | |
mFileUploadCallbackSecond.onReceiveValue(null); | |
} | |
mFileUploadCallbackSecond = fileUploadCallbackSecond; | |
Intent i = new Intent(Intent.ACTION_GET_CONTENT); | |
i.addCategory(Intent.CATEGORY_OPENABLE); | |
if (allowMultiple) { | |
if (Build.VERSION.SDK_INT >= 18) { | |
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); | |
} | |
} | |
i.setType(mUploadableFileTypes); | |
if (mFragment != null && mFragment.get() != null && Build.VERSION.SDK_INT >= 11) { | |
mFragment.get().startActivityForResult(Intent.createChooser(i, getFileUploadPromptLabel()), mRequestCodeFilePicker); | |
} else if (mActivity != null && mActivity.get() != null) { | |
mActivity.get().startActivityForResult(Intent.createChooser(i, getFileUploadPromptLabel()), mRequestCodeFilePicker); | |
} | |
} | |
/** | |
* Returns whether file uploads can be used on the current device (generally all platform versions except for 4.4) | |
* | |
* @return whether file uploads can be used | |
*/ | |
public static boolean isFileUploadAvailable() { | |
return isFileUploadAvailable(false); | |
} | |
/** | |
* Returns whether file uploads can be used on the current device (generally all platform versions except for 4.4) | |
* <p> | |
* On Android 4.4.3/4.4.4, file uploads may be possible but will come with a wrong MIME type | |
* | |
* @param needsCorrectMimeType whether a correct MIME type is required for file uploads or `application/octet-stream` is acceptable | |
* @return whether file uploads can be used | |
*/ | |
public static boolean isFileUploadAvailable(final boolean needsCorrectMimeType) { | |
if (Build.VERSION.SDK_INT == 19) { | |
final String platformVersion = (Build.VERSION.RELEASE == null) ? "" : Build.VERSION.RELEASE; | |
return !needsCorrectMimeType && (platformVersion.startsWith("4.4.3") || platformVersion.startsWith("4.4.4")); | |
} else { | |
return true; | |
} | |
} | |
/** | |
* Handles a download by loading the file from `fromUrl` and saving it to `toFilename` on the external storage | |
* <p> | |
* This requires the two permissions `android.permission.INTERNET` and `android.permission.WRITE_EXTERNAL_STORAGE` | |
* <p> | |
* Only supported on API level 9 (Android 2.3) and above | |
* | |
* @param context a valid `Context` reference | |
* @param fromUrl the URL of the file to download, e.g. the one from `AdvancedWebView.onDownloadRequested(...)` | |
* @param toFilename the name of the destination file where the download should be saved, e.g. `myImage.jpg` | |
* @return whether the download has been successfully handled or not | |
* @throws IllegalStateException if the storage or the target directory could not be found or accessed | |
*/ | |
@SuppressLint("NewApi") | |
public static boolean handleDownload(final Context context, final String fromUrl, final String toFilename) { | |
if (Build.VERSION.SDK_INT < 9) { | |
throw new RuntimeException("Method requires API level 9 or above"); | |
} | |
final Request request = new Request(Uri.parse(fromUrl)); | |
if (Build.VERSION.SDK_INT >= 11) { | |
request.allowScanningByMediaScanner(); | |
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); | |
} | |
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, toFilename); | |
final DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); | |
try { | |
try { | |
dm.enqueue(request); | |
} catch (SecurityException e) { | |
if (Build.VERSION.SDK_INT >= 11) { | |
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE); | |
} | |
dm.enqueue(request); | |
} | |
return true; | |
} | |
// if the download manager app has been disabled on the device | |
catch (IllegalArgumentException e) { | |
// show the settings screen where the user can enable the download manager app again | |
openAppSettings(context, AdvancedWebView.PACKAGE_NAME_DOWNLOAD_MANAGER); | |
return false; | |
} | |
} | |
@SuppressLint("NewApi") | |
private static boolean openAppSettings(final Context context, final String packageName) { | |
if (Build.VERSION.SDK_INT < 9) { | |
throw new RuntimeException("Method requires API level 9 or above"); | |
} | |
try { | |
final Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); | |
intent.setData(Uri.parse("package:" + packageName)); | |
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context.startActivity(intent); | |
return true; | |
} catch (Exception e) { | |
return false; | |
} | |
} | |
/** | |
* Wrapper for methods related to alternative browsers that have their own rendering engines | |
*/ | |
public static class Browsers { | |
/** | |
* Package name of an alternative browser that is installed on this device | |
*/ | |
private static String mAlternativePackage; | |
/** | |
* Returns whether there is an alternative browser with its own rendering engine currently installed | |
* | |
* @param context a valid `Context` reference | |
* @return whether there is an alternative browser or not | |
*/ | |
public static boolean hasAlternative(final Context context) { | |
return getAlternative(context) != null; | |
} | |
/** | |
* Returns the package name of an alternative browser with its own rendering engine or `null` | |
* | |
* @param context a valid `Context` reference | |
* @return the package name or `null` | |
*/ | |
public static String getAlternative(final Context context) { | |
if (mAlternativePackage != null) { | |
return mAlternativePackage; | |
} | |
final List<String> alternativeBrowsers = Arrays.asList(ALTERNATIVE_BROWSERS); | |
final List<ApplicationInfo> apps = context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA); | |
for (ApplicationInfo app : apps) { | |
if (!app.enabled) { | |
continue; | |
} | |
if (alternativeBrowsers.contains(app.packageName)) { | |
mAlternativePackage = app.packageName; | |
return app.packageName; | |
} | |
} | |
return null; | |
} | |
/** | |
* Opens the given URL in an alternative browser | |
* | |
* @param context a valid `Activity` reference | |
* @param url the URL to open | |
*/ | |
public static void openUrl(final Activity context, final String url) { | |
openUrl(context, url, false); | |
} | |
/** | |
* Opens the given URL in an alternative browser | |
* | |
* @param context a valid `Activity` reference | |
* @param url the URL to open | |
* @param withoutTransition whether to switch to the browser `Activity` without a transition | |
*/ | |
public static void openUrl(final Activity context, final String url, final boolean withoutTransition) { | |
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); | |
intent.setPackage(getAlternative(context)); | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context.startActivity(intent); | |
if (withoutTransition) { | |
context.overridePendingTransition(0, 0); | |
} | |
} | |
} | |
static void log(String msg) { | |
Log.e("AllureWebView", msg); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment