package yuki.control.extended; import android.os.Looper; import android.os.SystemClock; import android.content.ActivityNotFoundException; import android.text.TextUtils; import android.view.ViewGroup; import android.app.DownloadManager; import android.app.DownloadManager.Request; import android.os.Environment; import android.webkit.CookieManager; import java.util.Arrays; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import java.util.HashMap; import android.net.http.SslError; import android.view.InputEvent; import android.view.KeyEvent; import android.webkit.ClientCertRequest; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.URLUtil; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.os.Message; import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions.Callback; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.WebStorage.QuotaUpdater; import android.app.Fragment; import android.util.Base64; import android.os.Build; import android.webkit.DownloadListener; import android.graphics.Bitmap; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebViewClient; import android.webkit.WebSettings; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.webkit.WebView; import java.util.MissingResourceException; import java.util.Locale; import java.util.LinkedList; import java.util.Collection; import java.util.List; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.util.Map; /** * Advanced WebView component for Android that works as intended out of the box */ @SuppressWarnings("deprecation") public class WebViewEx 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 mActivity; protected WeakReference mFragment; protected Listener mListener; protected final List mPermittedHostnames = new LinkedList(); /** File upload callback for platform versions prior to Android 5.0 */ protected ValueCallback mFileUploadCallbackFirst; /** File upload callback for Android 5.0+ */ protected ValueCallback 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 mHttpHeaders = new HashMap(); public WebViewEx(Context context) { super(context); init(context); } public WebViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public WebViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void setUserAgent(String userAgent) { getSettings().setUserAgentString(userAgent); } public String getUserAgent() { return getSettings().getUserAgentString(); } 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); } 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); } 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 * * This does only affect the main requests, not the requests to included resources (e.g. images) * * If you later want to delete an HTTP header that was previously added this way, call `removeHttpHeader()` * * 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()` * * If you want to unset a pre-defined header, set it to an empty string with `addHttpHeader()` instead * * 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 collection) { mPermittedHostnames.addAll(collection); } public List 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); } @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) context); } mLanguageIso3 = getLanguageIso3(); 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); 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) { if (!isPermittedUrl(url)) { // if a listener is available if (mListener != null) { // inform the listener about the request mListener.onExternalPageRequest(url); } // 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 return true; } } final Uri uri = Uri.parse(url); final String scheme = uri.getScheme(); if (scheme != null) { final Intent externalSchemeIntent; if (scheme.equals("tel")) { externalSchemeIntent = new Intent(Intent.ACTION_DIAL, uri); } else if (scheme.equals("sms")) { externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); } else if (scheme.equals("mailto")) { externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); } else if (scheme.equals("whatsapp")) { externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); externalSchemeIntent.setPackage("com.whatsapp"); } else if(scheme.equals("weixin")){ externalSchemeIntent=new Intent(Intent.ACTION_VIEW,uri); } else { externalSchemeIntent = null; } if (externalSchemeIntent != null) { externalSchemeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { if (mActivity != null && mActivity.get() != null) { mActivity.get().startActivity(externalSchemeIntent); } else { getContext().startActivity(externalSchemeIntent); } } catch (ActivityNotFoundException ignored) {} // cancel the original request return true; } } if(uri.getHost().contains("wx.tenpay.com")){ return false; } // 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.onUnhandledInputEvent(view, event); } else { super.onUnhandledKeyEvent(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 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 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 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 filePathCallback, WebChromeClient.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 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); } } }); addJavascriptInterface(new YukiJSBridge(getContext()),"yuki"); } @Override public void loadUrl(final String url, Map 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 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 if (mPermittedHostnames.size() == 0) { // all hostnames are allowed return true; } final Uri parsedUrl = Uri.parse(url); // get the hostname of the URL that is to be checked final String actualHost = parsedUrl.getHost(); // if the hostname could not be determined, usually because the URL has been invalid if (actualHost == null) { return false; } // if the host contains invalid characters (e.g. a backslash) if (!actualHost.matches("^[a-zA-Z0-9._!~*')(;:&=+$,%\\[\\]-]*$")) { // prevent mismatches between interpretations by `Uri` and `WebView`, e.g. for `http://evil.example.com\.good.example.com/` return false; } // get the user information from the authority part of the URL that is to be checked final String actualUserInformation = parsedUrl.getUserInfo(); // if the user information contains invalid characters (e.g. a backslash) if (actualUserInformation != null && !actualUserInformation.matches("^[a-zA-Z0-9._!~*')(;:&=+$,%-]*$")) { // prevent mismatches between interpretations by `Uri` and `WebView`, e.g. for `http://evil.example.com\@good.example.com/` return false; } // 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; } } // 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 fileUploadCallbackFirst, final ValueCallback 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) * * 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 * * This requires the two permissions `android.permission.INTERNET` and `android.permission.WRITE_EXTERNAL_STORAGE` * * 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(DownloadManager.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(DownloadManager.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, WebViewEx.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 alternativeBrowsers = Arrays.asList(ALTERNATIVE_BROWSERS); final List 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); } } } }