├── Source ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── sample │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── values-v11 │ │ │ │ └── styles.xml │ │ │ └── values-v14 │ │ │ │ └── styles.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── im │ │ │ └── delight │ │ │ └── android │ │ │ └── examples │ │ │ └── webview │ │ │ └── MainActivity.java │ └── build.gradle ├── .editorconfig ├── library │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── im │ │ │ └── delight │ │ │ └── android │ │ │ └── webview │ │ │ └── AdvancedWebView.java │ └── build.gradle ├── build.gradle ├── .gitignore ├── gradlew.bat └── gradlew ├── .editorconfig ├── Migration.md ├── LICENSE └── README.md /Source/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' 2 | include ':sample' 3 | -------------------------------------------------------------------------------- /Source/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/Android-AdvancedWebView/HEAD/Source/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Source/sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Android-AdvancedWebView-Test 4 | 5 | 6 | -------------------------------------------------------------------------------- /Source/sample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/Android-AdvancedWebView/HEAD/Source/sample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Source/sample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/Android-AdvancedWebView/HEAD/Source/sample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Source/sample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delight-im/Android-AdvancedWebView/HEAD/Source/sample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /Source/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /Source/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /Source/library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Source/sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /Source/sample/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Source/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | google() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | jcenter() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/sample/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Source/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | # Custom 35 | .idea/ 36 | *.iml 37 | -------------------------------------------------------------------------------- /Source/sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion '26.0.2' 6 | 7 | defaultConfig { 8 | applicationId "im.delight.android.examples.webview" 9 | minSdkVersion 8 10 | targetSdkVersion 21 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile project(':library') 23 | } 24 | -------------------------------------------------------------------------------- /Migration.md: -------------------------------------------------------------------------------- 1 | # Migration 2 | 3 | ## From `v2.x.x` to `v3.x.x` 4 | 5 | * The license has been changed from the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) to the [MIT License](https://opensource.org/licenses/MIT). 6 | * The signature of `AdvancedWebView.Listener#onDownloadRequested` has changed from 7 | 8 | ```java 9 | void onDownloadRequested(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) 10 | ``` 11 | 12 | to 13 | 14 | ```java 15 | void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) 16 | ``` 17 | -------------------------------------------------------------------------------- /Source/sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Source/library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 21 6 | buildToolsVersion '26.0.2' 7 | 8 | defaultConfig { 9 | minSdkVersion 8 10 | targetSdkVersion 21 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | } 20 | 21 | task sourcesJar(type: Jar) { 22 | from android.sourceSets.main.java.srcDirs 23 | classifier = 'sources' 24 | } 25 | 26 | task javadoc(type: Javadoc) { 27 | failOnError false 28 | source = android.sourceSets.main.java.sourceFiles 29 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 30 | } 31 | 32 | task javadocJar(type: Jar, dependsOn: javadoc) { 33 | classifier = 'javadoc' 34 | from javadoc.destinationDir 35 | } 36 | 37 | artifacts { 38 | archives sourcesJar 39 | archives javadocJar 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) delight.im (https://www.delight.im/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | 11 | 12 | 13 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /Source/sample/src/main/java/im/delight/android/examples/webview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package im.delight.android.examples.webview; 2 | 3 | import im.delight.android.webview.AdvancedWebView; 4 | import android.webkit.WebChromeClient; 5 | import android.widget.Toast; 6 | import android.webkit.WebView; 7 | import android.view.View; 8 | import android.webkit.WebViewClient; 9 | import android.graphics.Bitmap; 10 | import android.content.Intent; 11 | import android.annotation.SuppressLint; 12 | import android.os.Bundle; 13 | import android.app.Activity; 14 | 15 | public class MainActivity extends Activity implements AdvancedWebView.Listener { 16 | 17 | private static final String TEST_PAGE_URL = "https://www.example.org/"; 18 | private AdvancedWebView mWebView; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | mWebView = (AdvancedWebView) findViewById(R.id.webview); 26 | mWebView.setListener(this, this); 27 | mWebView.setGeolocationEnabled(false); 28 | mWebView.setMixedContentAllowed(false); 29 | mWebView.setCookiesEnabled(true); 30 | mWebView.setThirdPartyCookiesEnabled(true); 31 | mWebView.setWebViewClient(new WebViewClient() { 32 | 33 | @Override 34 | public void onPageFinished(WebView view, String url) { 35 | Toast.makeText(MainActivity.this, "Finished loading", Toast.LENGTH_SHORT).show(); 36 | } 37 | 38 | }); 39 | mWebView.setWebChromeClient(new WebChromeClient() { 40 | 41 | @Override 42 | public void onReceivedTitle(WebView view, String title) { 43 | super.onReceivedTitle(view, title); 44 | Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show(); 45 | } 46 | 47 | }); 48 | mWebView.addHttpHeader("X-Requested-With", ""); 49 | mWebView.loadUrl(TEST_PAGE_URL); 50 | } 51 | 52 | @SuppressLint("NewApi") 53 | @Override 54 | protected void onResume() { 55 | super.onResume(); 56 | mWebView.onResume(); 57 | // ... 58 | } 59 | 60 | @SuppressLint("NewApi") 61 | @Override 62 | protected void onPause() { 63 | mWebView.onPause(); 64 | // ... 65 | super.onPause(); 66 | } 67 | 68 | @Override 69 | protected void onDestroy() { 70 | mWebView.onDestroy(); 71 | // ... 72 | super.onDestroy(); 73 | } 74 | 75 | @Override 76 | protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 77 | super.onActivityResult(requestCode, resultCode, intent); 78 | mWebView.onActivityResult(requestCode, resultCode, intent); 79 | // ... 80 | } 81 | 82 | @Override 83 | public void onBackPressed() { 84 | if (!mWebView.onBackPressed()) { return; } 85 | // ... 86 | super.onBackPressed(); 87 | } 88 | 89 | @Override 90 | public void onPageStarted(String url, Bitmap favicon) { 91 | mWebView.setVisibility(View.INVISIBLE); 92 | } 93 | 94 | @Override 95 | public void onPageFinished(String url) { 96 | mWebView.setVisibility(View.VISIBLE); 97 | } 98 | 99 | @Override 100 | public void onPageError(int errorCode, String description, String failingUrl) { 101 | Toast.makeText(MainActivity.this, "onPageError(errorCode = "+errorCode+", description = "+description+", failingUrl = "+failingUrl+")", Toast.LENGTH_SHORT).show(); 102 | } 103 | 104 | @Override 105 | public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { 106 | Toast.makeText(MainActivity.this, "onDownloadRequested(url = "+url+", suggestedFilename = "+suggestedFilename+", mimeType = "+mimeType+", contentLength = "+contentLength+", contentDisposition = "+contentDisposition+", userAgent = "+userAgent+")", Toast.LENGTH_LONG).show(); 107 | 108 | /*if (AdvancedWebView.handleDownload(this, url, suggestedFilename)) { 109 | // download successfully handled 110 | } 111 | else { 112 | // download couldn't be handled because user has disabled download manager app on the device 113 | }*/ 114 | } 115 | 116 | @Override 117 | public void onExternalPageRequest(String url) { 118 | Toast.makeText(MainActivity.this, "onExternalPageRequest(url = "+url+")", Toast.LENGTH_SHORT).show(); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Source/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvancedWebView 2 | 3 | Enhanced WebView component for Android that works as intended out of the box 4 | 5 | ## Requirements 6 | 7 | * Android 2.2+ 8 | 9 | ## Installation 10 | 11 | * Add this library to your project 12 | * Declare the Gradle repository in your root `build.gradle` 13 | 14 | ```gradle 15 | allprojects { 16 | repositories { 17 | maven { url "https://jitpack.io" } 18 | } 19 | } 20 | ``` 21 | 22 | * Declare the Gradle dependency in your app module's `build.gradle` 23 | 24 | ```gradle 25 | dependencies { 26 | implementation 'com.github.delight-im:Android-AdvancedWebView:v3.2.1' 27 | } 28 | ``` 29 | 30 | ## Usage 31 | 32 | ### AndroidManifest.xml 33 | 34 | ```xml 35 | 36 | ``` 37 | 38 | ### Layout (XML) 39 | 40 | ```xml 41 | 45 | ``` 46 | 47 | ### Activity (Java) 48 | 49 | #### Without Fragments 50 | 51 | ```java 52 | public class MyActivity extends Activity implements AdvancedWebView.Listener { 53 | 54 | private AdvancedWebView mWebView; 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | setContentView(R.layout.activity_my); 60 | 61 | mWebView = (AdvancedWebView) findViewById(R.id.webview); 62 | mWebView.setListener(this, this); 63 | mWebView.setMixedContentAllowed(false); 64 | mWebView.loadUrl("http://www.example.org/"); 65 | 66 | // ... 67 | } 68 | 69 | @SuppressLint("NewApi") 70 | @Override 71 | protected void onResume() { 72 | super.onResume(); 73 | mWebView.onResume(); 74 | // ... 75 | } 76 | 77 | @SuppressLint("NewApi") 78 | @Override 79 | protected void onPause() { 80 | mWebView.onPause(); 81 | // ... 82 | super.onPause(); 83 | } 84 | 85 | @Override 86 | protected void onDestroy() { 87 | mWebView.onDestroy(); 88 | // ... 89 | super.onDestroy(); 90 | } 91 | 92 | @Override 93 | protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 94 | super.onActivityResult(requestCode, resultCode, intent); 95 | mWebView.onActivityResult(requestCode, resultCode, intent); 96 | // ... 97 | } 98 | 99 | @Override 100 | public void onBackPressed() { 101 | if (!mWebView.onBackPressed()) { return; } 102 | // ... 103 | super.onBackPressed(); 104 | } 105 | 106 | @Override 107 | public void onPageStarted(String url, Bitmap favicon) { } 108 | 109 | @Override 110 | public void onPageFinished(String url) { } 111 | 112 | @Override 113 | public void onPageError(int errorCode, String description, String failingUrl) { } 114 | 115 | @Override 116 | public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { } 117 | 118 | @Override 119 | public void onExternalPageRequest(String url) { } 120 | 121 | } 122 | ``` 123 | 124 | #### With Fragments (`android.app.Fragment`) 125 | 126 | **Note:** If you're using the `Fragment` class from the support library (`android.support.v4.app.Fragment`), please refer to the next section (see below) instead of this one. 127 | 128 | ```java 129 | public class MyFragment extends Fragment implements AdvancedWebView.Listener { 130 | 131 | private AdvancedWebView mWebView; 132 | 133 | public MyFragment() { } 134 | 135 | @Override 136 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 137 | View rootView = inflater.inflate(R.layout.fragment_my, container, false); 138 | 139 | mWebView = (AdvancedWebView) rootView.findViewById(R.id.webview); 140 | mWebView.setListener(this, this); 141 | mWebView.setMixedContentAllowed(false); 142 | mWebView.loadUrl("http://www.example.org/"); 143 | 144 | // ... 145 | 146 | return rootView; 147 | } 148 | 149 | @SuppressLint("NewApi") 150 | @Override 151 | public void onResume() { 152 | super.onResume(); 153 | mWebView.onResume(); 154 | // ... 155 | } 156 | 157 | @SuppressLint("NewApi") 158 | @Override 159 | public void onPause() { 160 | mWebView.onPause(); 161 | // ... 162 | super.onPause(); 163 | } 164 | 165 | @Override 166 | public void onDestroy() { 167 | mWebView.onDestroy(); 168 | // ... 169 | super.onDestroy(); 170 | } 171 | 172 | @Override 173 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 174 | super.onActivityResult(requestCode, resultCode, intent); 175 | mWebView.onActivityResult(requestCode, resultCode, intent); 176 | // ... 177 | } 178 | 179 | @Override 180 | public void onPageStarted(String url, Bitmap favicon) { } 181 | 182 | @Override 183 | public void onPageFinished(String url) { } 184 | 185 | @Override 186 | public void onPageError(int errorCode, String description, String failingUrl) { } 187 | 188 | @Override 189 | public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { } 190 | 191 | @Override 192 | public void onExternalPageRequest(String url) { } 193 | 194 | } 195 | ``` 196 | 197 | #### With Fragments from the support library (`android.support.v4.app.Fragment`) 198 | 199 | * Use the code for normal `Fragment` usage as shown above 200 | * Change 201 | 202 | ```java 203 | mWebView.setListener(this, this); 204 | ``` 205 | 206 | to 207 | 208 | ```java 209 | mWebView.setListener(getActivity(), this); 210 | ``` 211 | 212 | * Add the following code to the parent `FragmentActivity` in order to forward the results from the `FragmentActivity` to the appropriate `Fragment` instance 213 | 214 | ```java 215 | public class MyActivity extends FragmentActivity implements AdvancedWebView.Listener { 216 | 217 | @Override 218 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 219 | super.onActivityResult(requestCode, resultCode, intent); 220 | if (mFragment != null) { 221 | mFragment.onActivityResult(requestCode, resultCode, intent); 222 | } 223 | } 224 | 225 | } 226 | ``` 227 | 228 | ### ProGuard (if enabled) 229 | 230 | ``` 231 | -keep class * extends android.webkit.WebChromeClient { *; } 232 | -dontwarn im.delight.android.webview.** 233 | ``` 234 | 235 | ### Cleartext (non-HTTPS) traffic 236 | 237 | If you want to serve sites or just single resources over plain `http` instead of `https`, there’s usually nothing to do if you’re targeting Android 8.1 (API level 27) or earlier. On Android 9 (API level 28) and later, however, [cleartext support is disabled by default](https://developer.android.com/training/articles/security-config). You may have to set `android:usesCleartextTraffic="true"` on the `` element in `AndroidManifest.xml` or provide a custom [network security configuration](https://developer.android.com/training/articles/security-config). 238 | 239 | ## Features 240 | 241 | * Optimized for best performance and security 242 | * Features are patched across Android versions 243 | * File uploads are handled automatically (check availability with `AdvancedWebView.isFileUploadAvailable()`) 244 | * Multiple file uploads via single input fields (`multiple` attribute in HTML) are supported on Android 5.0+. The application that is used to pick the files (i.e. usually a gallery or file manager app) must provide controls for selecting multiple files, which some apps don't. 245 | * JavaScript and WebStorage are enabled by default 246 | * Includes localizations for the 25 most widely spoken languages 247 | * Receive callbacks when pages start/finish loading or have errors 248 | 249 | ```java 250 | @Override 251 | public void onPageStarted(String url, Bitmap favicon) { 252 | // a new page started loading 253 | } 254 | 255 | @Override 256 | public void onPageFinished(String url) { 257 | // the new page finished loading 258 | } 259 | 260 | @Override 261 | public void onPageError(int errorCode, String description, String failingUrl) { 262 | // the new page failed to load 263 | } 264 | ``` 265 | 266 | * Downloads are handled automatically and can be listened to 267 | 268 | ```java 269 | @Override 270 | public void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent) { 271 | // some file is available for download 272 | // either handle the download yourself or use the code below 273 | 274 | if (AdvancedWebView.handleDownload(this, url, suggestedFilename)) { 275 | // download successfully handled 276 | } 277 | else { 278 | // download couldn't be handled because user has disabled download manager app on the device 279 | // TODO show some notice to the user 280 | } 281 | } 282 | ``` 283 | 284 | * Enable geolocation support (needs ``) 285 | 286 | ```java 287 | mWebView.setGeolocationEnabled(true); 288 | ``` 289 | 290 | * Add custom HTTP headers in addition to the ones sent by the web browser implementation 291 | 292 | ```java 293 | mWebView.addHttpHeader("X-Requested-With", "My wonderful app"); 294 | ``` 295 | 296 | * Define a custom set of permitted hostnames and receive callbacks for all other hostnames 297 | 298 | ```java 299 | mWebView.addPermittedHostname("example.org"); 300 | ``` 301 | 302 | and 303 | 304 | ```java 305 | @Override 306 | public void onExternalPageRequest(String url) { 307 | // the user tried to open a page from a non-permitted hostname 308 | } 309 | ``` 310 | 311 | * Prevent caching of HTML pages 312 | 313 | ```java 314 | boolean preventCaching = true; 315 | mWebView.loadUrl("http://www.example.org/", preventCaching); 316 | ``` 317 | 318 | * Check for alternative browsers installed on the device 319 | 320 | ```java 321 | if (AdvancedWebView.Browsers.hasAlternative(this)) { 322 | AdvancedWebView.Browsers.openUrl(this, "http://www.example.org/"); 323 | } 324 | ``` 325 | 326 | * Disable cookies 327 | 328 | ```java 329 | // disable third-party cookies only 330 | mWebView.setThirdPartyCookiesEnabled(false); 331 | // or disable cookies in general 332 | mWebView.setCookiesEnabled(false); 333 | ``` 334 | 335 | * Allow or disallow (both passive and active) mixed content (HTTP content being loaded inside HTTPS sites) 336 | 337 | ```java 338 | mWebView.setMixedContentAllowed(true); 339 | // or 340 | mWebView.setMixedContentAllowed(false); 341 | ``` 342 | 343 | * Switch between mobile and desktop mode 344 | 345 | ```java 346 | mWebView.setDesktopMode(true); 347 | // or 348 | // mWebView.setDesktopMode(false); 349 | ``` 350 | 351 | * Load HTML file from “assets” (e.g. at `app/src/main/assets/html/index.html`) 352 | 353 | ```java 354 | mWebView.loadUrl("file:///android_asset/html/index.html"); 355 | ``` 356 | 357 | * Load HTML file from SD card 358 | 359 | ```java 360 | // 361 | 362 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 363 | mWebView.getSettings().setAllowFileAccess(true); 364 | mWebView.loadUrl("file:///sdcard/Android/data/com.my.app/my_folder/index.html"); 365 | } 366 | ``` 367 | 368 | * Load HTML source text and display as page 369 | 370 | ```java 371 | myWebView.loadHtml("..."); 372 | 373 | // or 374 | 375 | final String myBaseUrl = "http://www.example.com/"; 376 | myWebView.loadHtml("...", myBaseUrl); 377 | ``` 378 | 379 | * Enable multi-window support 380 | 381 | ```java 382 | myWebView.getSettings().setSupportMultipleWindows(true); 383 | // myWebView.getSettings().setJavaScriptEnabled(true); 384 | // myWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); 385 | 386 | myWebView.setWebChromeClient(new WebChromeClient() { 387 | 388 | @Override 389 | public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 390 | AdvancedWebView newWebView = new AdvancedWebView(MyNewActivity.this); 391 | // myParentLayout.addView(newWebView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 392 | WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 393 | transport.setWebView(newWebView); 394 | resultMsg.sendToTarget(); 395 | 396 | return true; 397 | } 398 | 399 | } 400 | ``` 401 | 402 | ## Contributing 403 | 404 | All contributions are welcome! If you wish to contribute, please create an issue first so that your feature, problem or question can be discussed. 405 | 406 | ## License 407 | 408 | This project is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT). 409 | -------------------------------------------------------------------------------- /Source/library/src/main/java/im/delight/android/webview/AdvancedWebView.java: -------------------------------------------------------------------------------- 1 | package im.delight.android.webview; 2 | 3 | /* 4 | * Android-AdvancedWebView (https://github.com/delight-im/Android-AdvancedWebView) 5 | * Copyright (c) delight.im (https://www.delight.im/) 6 | * Licensed under the MIT License (https://opensource.org/licenses/MIT) 7 | */ 8 | 9 | import android.content.ActivityNotFoundException; 10 | import android.view.ViewGroup; 11 | import android.app.DownloadManager; 12 | import android.app.DownloadManager.Request; 13 | import android.os.Environment; 14 | import android.webkit.CookieManager; 15 | import java.util.Arrays; 16 | import android.content.pm.ApplicationInfo; 17 | import android.content.pm.PackageManager; 18 | import java.util.HashMap; 19 | import android.net.http.SslError; 20 | import android.view.InputEvent; 21 | import android.view.KeyEvent; 22 | import android.webkit.ClientCertRequest; 23 | import android.webkit.HttpAuthHandler; 24 | import android.webkit.SslErrorHandler; 25 | import android.webkit.URLUtil; 26 | import android.webkit.WebResourceRequest; 27 | import android.webkit.WebResourceResponse; 28 | import android.os.Message; 29 | import android.view.View; 30 | import android.webkit.ConsoleMessage; 31 | import android.webkit.GeolocationPermissions.Callback; 32 | import android.webkit.JsPromptResult; 33 | import android.webkit.JsResult; 34 | import android.webkit.PermissionRequest; 35 | import android.webkit.WebStorage.QuotaUpdater; 36 | import android.app.Fragment; 37 | import android.util.Base64; 38 | import android.os.Build; 39 | import android.webkit.DownloadListener; 40 | import android.graphics.Bitmap; 41 | import android.app.Activity; 42 | import android.content.Intent; 43 | import android.net.Uri; 44 | import android.webkit.ValueCallback; 45 | import android.webkit.WebChromeClient; 46 | import android.webkit.WebViewClient; 47 | import android.webkit.WebSettings; 48 | import android.annotation.SuppressLint; 49 | import android.content.Context; 50 | import android.util.AttributeSet; 51 | import android.webkit.WebView; 52 | import java.util.MissingResourceException; 53 | import java.util.Locale; 54 | import java.util.LinkedList; 55 | import java.util.Collection; 56 | import java.util.List; 57 | import java.io.UnsupportedEncodingException; 58 | import java.lang.ref.WeakReference; 59 | import java.util.Map; 60 | 61 | /** Advanced WebView component for Android that works as intended out of the box */ 62 | @SuppressWarnings("deprecation") 63 | public class AdvancedWebView extends WebView { 64 | 65 | public interface Listener { 66 | void onPageStarted(String url, Bitmap favicon); 67 | void onPageFinished(String url); 68 | void onPageError(int errorCode, String description, String failingUrl); 69 | void onDownloadRequested(String url, String suggestedFilename, String mimeType, long contentLength, String contentDisposition, String userAgent); 70 | void onExternalPageRequest(String url); 71 | } 72 | 73 | public static final String PACKAGE_NAME_DOWNLOAD_MANAGER = "com.android.providers.downloads"; 74 | protected static final int REQUEST_CODE_FILE_PICKER = 51426; 75 | protected static final String DATABASES_SUB_FOLDER = "/databases"; 76 | protected static final String LANGUAGE_DEFAULT_ISO3 = "eng"; 77 | protected static final String CHARSET_DEFAULT = "UTF-8"; 78 | /** Alternative browsers that have their own rendering engine and *may* be installed on this device */ 79 | 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" }; 80 | protected WeakReference mActivity; 81 | protected WeakReference mFragment; 82 | protected Listener mListener; 83 | protected final List mPermittedHostnames = new LinkedList(); 84 | /** File upload callback for platform versions prior to Android 5.0 */ 85 | protected ValueCallback mFileUploadCallbackFirst; 86 | /** File upload callback for Android 5.0+ */ 87 | protected ValueCallback mFileUploadCallbackSecond; 88 | protected long mLastError; 89 | protected String mLanguageIso3; 90 | protected int mRequestCodeFilePicker = REQUEST_CODE_FILE_PICKER; 91 | protected WebViewClient mCustomWebViewClient; 92 | protected WebChromeClient mCustomWebChromeClient; 93 | protected boolean mGeolocationEnabled; 94 | protected String mUploadableFileTypes = "*/*"; 95 | protected final Map mHttpHeaders = new HashMap(); 96 | 97 | public AdvancedWebView(Context context) { 98 | super(context); 99 | init(context); 100 | } 101 | 102 | public AdvancedWebView(Context context, AttributeSet attrs) { 103 | super(context, attrs); 104 | init(context); 105 | } 106 | 107 | public AdvancedWebView(Context context, AttributeSet attrs, int defStyleAttr) { 108 | super(context, attrs, defStyleAttr); 109 | init(context); 110 | } 111 | 112 | public void setListener(final Activity activity, final Listener listener) { 113 | setListener(activity, listener, REQUEST_CODE_FILE_PICKER); 114 | } 115 | 116 | public void setListener(final Activity activity, final Listener listener, final int requestCodeFilePicker) { 117 | if (activity != null) { 118 | mActivity = new WeakReference(activity); 119 | } 120 | else { 121 | mActivity = null; 122 | } 123 | 124 | setListener(listener, requestCodeFilePicker); 125 | } 126 | 127 | public void setListener(final Fragment fragment, final Listener listener) { 128 | setListener(fragment, listener, REQUEST_CODE_FILE_PICKER); 129 | } 130 | 131 | public void setListener(final Fragment fragment, final Listener listener, final int requestCodeFilePicker) { 132 | if (fragment != null) { 133 | mFragment = new WeakReference(fragment); 134 | } 135 | else { 136 | mFragment = null; 137 | } 138 | 139 | setListener(listener, requestCodeFilePicker); 140 | } 141 | 142 | protected void setListener(final Listener listener, final int requestCodeFilePicker) { 143 | mListener = listener; 144 | mRequestCodeFilePicker = requestCodeFilePicker; 145 | } 146 | 147 | @Override 148 | public void setWebViewClient(final WebViewClient client) { 149 | mCustomWebViewClient = client; 150 | } 151 | 152 | @Override 153 | public void setWebChromeClient(final WebChromeClient client) { 154 | mCustomWebChromeClient = client; 155 | } 156 | 157 | @SuppressLint("SetJavaScriptEnabled") 158 | public void setGeolocationEnabled(final boolean enabled) { 159 | if (enabled) { 160 | getSettings().setJavaScriptEnabled(true); 161 | getSettings().setGeolocationEnabled(true); 162 | setGeolocationDatabasePath(); 163 | } 164 | 165 | mGeolocationEnabled = enabled; 166 | } 167 | 168 | @SuppressLint("NewApi") 169 | protected void setGeolocationDatabasePath() { 170 | final Activity activity; 171 | 172 | if (mFragment != null && mFragment.get() != null && Build.VERSION.SDK_INT >= 11 && mFragment.get().getActivity() != null) { 173 | activity = mFragment.get().getActivity(); 174 | } 175 | else if (mActivity != null && mActivity.get() != null) { 176 | activity = mActivity.get(); 177 | } 178 | else { 179 | return; 180 | } 181 | 182 | getSettings().setGeolocationDatabasePath(activity.getFilesDir().getPath()); 183 | } 184 | 185 | public void setUploadableFileTypes(final String mimeType) { 186 | mUploadableFileTypes = mimeType; 187 | } 188 | 189 | /** 190 | * Loads and displays the provided HTML source text 191 | * 192 | * @param html the HTML source text to load 193 | */ 194 | public void loadHtml(final String html) { 195 | loadHtml(html, null); 196 | } 197 | 198 | /** 199 | * Loads and displays the provided HTML source text 200 | * 201 | * @param html the HTML source text to load 202 | * @param baseUrl the URL to use as the page's base URL 203 | */ 204 | public void loadHtml(final String html, final String baseUrl) { 205 | loadHtml(html, baseUrl, null); 206 | } 207 | 208 | /** 209 | * Loads and displays the provided HTML source text 210 | * 211 | * @param html the HTML source text to load 212 | * @param baseUrl the URL to use as the page's base URL 213 | * @param historyUrl the URL to use for the page's history entry 214 | */ 215 | public void loadHtml(final String html, final String baseUrl, final String historyUrl) { 216 | loadHtml(html, baseUrl, historyUrl, "utf-8"); 217 | } 218 | 219 | /** 220 | * Loads and displays the provided HTML source text 221 | * 222 | * @param html the HTML source text to load 223 | * @param baseUrl the URL to use as the page's base URL 224 | * @param historyUrl the URL to use for the page's history entry 225 | * @param encoding the encoding or charset of the HTML source text 226 | */ 227 | public void loadHtml(final String html, final String baseUrl, final String historyUrl, final String encoding) { 228 | loadDataWithBaseURL(baseUrl, html, "text/html", encoding, historyUrl); 229 | } 230 | 231 | @SuppressLint("NewApi") 232 | @SuppressWarnings("all") 233 | public void onResume() { 234 | if (Build.VERSION.SDK_INT >= 11) { 235 | super.onResume(); 236 | } 237 | resumeTimers(); 238 | } 239 | 240 | @SuppressLint("NewApi") 241 | @SuppressWarnings("all") 242 | public void onPause() { 243 | pauseTimers(); 244 | if (Build.VERSION.SDK_INT >= 11) { 245 | super.onPause(); 246 | } 247 | } 248 | 249 | public void onDestroy() { 250 | // try to remove this view from its parent first 251 | try { 252 | ((ViewGroup) getParent()).removeView(this); 253 | } 254 | catch (Exception ignored) { } 255 | 256 | // then try to remove all child views from this view 257 | try { 258 | removeAllViews(); 259 | } 260 | catch (Exception ignored) { } 261 | 262 | // and finally destroy this view 263 | destroy(); 264 | } 265 | 266 | public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { 267 | if (requestCode == mRequestCodeFilePicker) { 268 | if (resultCode == Activity.RESULT_OK) { 269 | if (intent != null) { 270 | if (mFileUploadCallbackFirst != null) { 271 | mFileUploadCallbackFirst.onReceiveValue(intent.getData()); 272 | mFileUploadCallbackFirst = null; 273 | } 274 | else if (mFileUploadCallbackSecond != null) { 275 | Uri[] dataUris = null; 276 | 277 | try { 278 | if (intent.getDataString() != null) { 279 | dataUris = new Uri[] { Uri.parse(intent.getDataString()) }; 280 | } 281 | else { 282 | if (Build.VERSION.SDK_INT >= 16) { 283 | if (intent.getClipData() != null) { 284 | final int numSelectedFiles = intent.getClipData().getItemCount(); 285 | 286 | dataUris = new Uri[numSelectedFiles]; 287 | 288 | for (int i = 0; i < numSelectedFiles; i++) { 289 | dataUris[i] = intent.getClipData().getItemAt(i).getUri(); 290 | } 291 | } 292 | } 293 | } 294 | } 295 | catch (Exception ignored) { } 296 | 297 | mFileUploadCallbackSecond.onReceiveValue(dataUris); 298 | mFileUploadCallbackSecond = null; 299 | } 300 | } 301 | } 302 | else { 303 | if (mFileUploadCallbackFirst != null) { 304 | mFileUploadCallbackFirst.onReceiveValue(null); 305 | mFileUploadCallbackFirst = null; 306 | } 307 | else if (mFileUploadCallbackSecond != null) { 308 | mFileUploadCallbackSecond.onReceiveValue(null); 309 | mFileUploadCallbackSecond = null; 310 | } 311 | } 312 | } 313 | } 314 | 315 | /** 316 | * Adds an additional HTTP header that will be sent along with every HTTP `GET` request 317 | * 318 | * This does only affect the main requests, not the requests to included resources (e.g. images) 319 | * 320 | * If you later want to delete an HTTP header that was previously added this way, call `removeHttpHeader()` 321 | * 322 | * The `WebView` implementation may in some cases overwrite headers that you set or unset 323 | * 324 | * @param name the name of the HTTP header to add 325 | * @param value the value of the HTTP header to send 326 | */ 327 | public void addHttpHeader(final String name, final String value) { 328 | mHttpHeaders.put(name, value); 329 | } 330 | 331 | /** 332 | * Removes one of the HTTP headers that have previously been added via `addHttpHeader()` 333 | * 334 | * If you want to unset a pre-defined header, set it to an empty string with `addHttpHeader()` instead 335 | * 336 | * The `WebView` implementation may in some cases overwrite headers that you set or unset 337 | * 338 | * @param name the name of the HTTP header to remove 339 | */ 340 | public void removeHttpHeader(final String name) { 341 | mHttpHeaders.remove(name); 342 | } 343 | 344 | public void addPermittedHostname(String hostname) { 345 | mPermittedHostnames.add(hostname); 346 | } 347 | 348 | public void addPermittedHostnames(Collection collection) { 349 | mPermittedHostnames.addAll(collection); 350 | } 351 | 352 | public List getPermittedHostnames() { 353 | return mPermittedHostnames; 354 | } 355 | 356 | public void removePermittedHostname(String hostname) { 357 | mPermittedHostnames.remove(hostname); 358 | } 359 | 360 | public void clearPermittedHostnames() { 361 | mPermittedHostnames.clear(); 362 | } 363 | 364 | public boolean onBackPressed() { 365 | if (canGoBack()) { 366 | goBack(); 367 | return false; 368 | } 369 | else { 370 | return true; 371 | } 372 | } 373 | 374 | @SuppressLint("NewApi") 375 | protected static void setAllowAccessFromFileUrls(final WebSettings webSettings, final boolean allowed) { 376 | if (Build.VERSION.SDK_INT >= 16) { 377 | webSettings.setAllowFileAccessFromFileURLs(allowed); 378 | webSettings.setAllowUniversalAccessFromFileURLs(allowed); 379 | } 380 | } 381 | 382 | @SuppressWarnings("static-method") 383 | public void setCookiesEnabled(final boolean enabled) { 384 | CookieManager.getInstance().setAcceptCookie(enabled); 385 | } 386 | 387 | @SuppressLint("NewApi") 388 | public void setThirdPartyCookiesEnabled(final boolean enabled) { 389 | if (Build.VERSION.SDK_INT >= 21) { 390 | CookieManager.getInstance().setAcceptThirdPartyCookies(this, enabled); 391 | } 392 | } 393 | 394 | public void setMixedContentAllowed(final boolean allowed) { 395 | setMixedContentAllowed(getSettings(), allowed); 396 | } 397 | 398 | @SuppressWarnings("static-method") 399 | @SuppressLint("NewApi") 400 | protected void setMixedContentAllowed(final WebSettings webSettings, final boolean allowed) { 401 | if (Build.VERSION.SDK_INT >= 21) { 402 | webSettings.setMixedContentMode(allowed ? WebSettings.MIXED_CONTENT_ALWAYS_ALLOW : WebSettings.MIXED_CONTENT_NEVER_ALLOW); 403 | } 404 | } 405 | 406 | public void setDesktopMode(final boolean enabled) { 407 | final WebSettings webSettings = getSettings(); 408 | 409 | final String newUserAgent; 410 | if (enabled) { 411 | newUserAgent = webSettings.getUserAgentString().replace("Mobile", "eliboM").replace("Android", "diordnA"); 412 | } 413 | else { 414 | newUserAgent = webSettings.getUserAgentString().replace("eliboM", "Mobile").replace("diordnA", "Android"); 415 | } 416 | 417 | webSettings.setUserAgentString(newUserAgent); 418 | webSettings.setUseWideViewPort(enabled); 419 | webSettings.setLoadWithOverviewMode(enabled); 420 | webSettings.setSupportZoom(enabled); 421 | webSettings.setBuiltInZoomControls(enabled); 422 | } 423 | 424 | @SuppressLint({ "SetJavaScriptEnabled" }) 425 | protected void init(Context context) { 426 | // in IDE's preview mode 427 | if (isInEditMode()) { 428 | // do not run the code from this method 429 | return; 430 | } 431 | 432 | if (context instanceof Activity) { 433 | mActivity = new WeakReference((Activity) context); 434 | } 435 | 436 | mLanguageIso3 = getLanguageIso3(); 437 | 438 | setFocusable(true); 439 | setFocusableInTouchMode(true); 440 | 441 | setSaveEnabled(true); 442 | 443 | final String filesDir = context.getFilesDir().getPath(); 444 | final String databaseDir = filesDir.substring(0, filesDir.lastIndexOf("/")) + DATABASES_SUB_FOLDER; 445 | 446 | final WebSettings webSettings = getSettings(); 447 | webSettings.setAllowFileAccess(false); 448 | setAllowAccessFromFileUrls(webSettings, false); 449 | webSettings.setBuiltInZoomControls(false); 450 | webSettings.setJavaScriptEnabled(true); 451 | webSettings.setDomStorageEnabled(true); 452 | if (Build.VERSION.SDK_INT < 18) { 453 | webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); 454 | } 455 | webSettings.setDatabaseEnabled(true); 456 | if (Build.VERSION.SDK_INT < 19) { 457 | webSettings.setDatabasePath(databaseDir); 458 | } 459 | 460 | if (Build.VERSION.SDK_INT >= 21) { 461 | webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); 462 | } 463 | 464 | setThirdPartyCookiesEnabled(true); 465 | 466 | super.setWebViewClient(new WebViewClient() { 467 | 468 | @Override 469 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 470 | if (!hasError()) { 471 | if (mListener != null) { 472 | mListener.onPageStarted(url, favicon); 473 | } 474 | } 475 | 476 | if (mCustomWebViewClient != null) { 477 | mCustomWebViewClient.onPageStarted(view, url, favicon); 478 | } 479 | } 480 | 481 | @Override 482 | public void onPageFinished(WebView view, String url) { 483 | if (!hasError()) { 484 | if (mListener != null) { 485 | mListener.onPageFinished(url); 486 | } 487 | } 488 | 489 | if (mCustomWebViewClient != null) { 490 | mCustomWebViewClient.onPageFinished(view, url); 491 | } 492 | } 493 | 494 | @Override 495 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 496 | setLastError(); 497 | 498 | if (mListener != null) { 499 | mListener.onPageError(errorCode, description, failingUrl); 500 | } 501 | 502 | if (mCustomWebViewClient != null) { 503 | mCustomWebViewClient.onReceivedError(view, errorCode, description, failingUrl); 504 | } 505 | } 506 | 507 | @Override 508 | public boolean shouldOverrideUrlLoading(final WebView view, final String url) { 509 | if (!isPermittedUrl(url)) { 510 | // if a listener is available 511 | if (mListener != null) { 512 | // inform the listener about the request 513 | mListener.onExternalPageRequest(url); 514 | } 515 | 516 | // cancel the original request 517 | return true; 518 | } 519 | 520 | // if there is a user-specified handler available 521 | if (mCustomWebViewClient != null) { 522 | // if the user-specified handler asks to override the request 523 | if (mCustomWebViewClient.shouldOverrideUrlLoading(view, url)) { 524 | // cancel the original request 525 | return true; 526 | } 527 | } 528 | 529 | final Uri uri = Uri.parse(url); 530 | final String scheme = uri.getScheme(); 531 | 532 | if (scheme != null) { 533 | final Intent externalSchemeIntent; 534 | 535 | if (scheme.equals("tel")) { 536 | externalSchemeIntent = new Intent(Intent.ACTION_DIAL, uri); 537 | } 538 | else if (scheme.equals("sms")) { 539 | externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); 540 | } 541 | else if (scheme.equals("mailto")) { 542 | externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); 543 | } 544 | else if (scheme.equals("whatsapp")) { 545 | externalSchemeIntent = new Intent(Intent.ACTION_SENDTO, uri); 546 | externalSchemeIntent.setPackage("com.whatsapp"); 547 | } 548 | else { 549 | externalSchemeIntent = null; 550 | } 551 | 552 | if (externalSchemeIntent != null) { 553 | externalSchemeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 554 | 555 | try { 556 | if (mActivity != null && mActivity.get() != null) { 557 | mActivity.get().startActivity(externalSchemeIntent); 558 | } 559 | else { 560 | getContext().startActivity(externalSchemeIntent); 561 | } 562 | } 563 | catch (ActivityNotFoundException ignored) {} 564 | 565 | // cancel the original request 566 | return true; 567 | } 568 | } 569 | 570 | // route the request through the custom URL loading method 571 | view.loadUrl(url); 572 | 573 | // cancel the original request 574 | return true; 575 | } 576 | 577 | @Override 578 | public void onLoadResource(WebView view, String url) { 579 | if (mCustomWebViewClient != null) { 580 | mCustomWebViewClient.onLoadResource(view, url); 581 | } 582 | else { 583 | super.onLoadResource(view, url); 584 | } 585 | } 586 | 587 | @SuppressLint("NewApi") 588 | @SuppressWarnings("all") 589 | public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 590 | if (Build.VERSION.SDK_INT >= 11) { 591 | if (mCustomWebViewClient != null) { 592 | return mCustomWebViewClient.shouldInterceptRequest(view, url); 593 | } 594 | else { 595 | return super.shouldInterceptRequest(view, url); 596 | } 597 | } 598 | else { 599 | return null; 600 | } 601 | } 602 | 603 | @SuppressLint("NewApi") 604 | @SuppressWarnings("all") 605 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 606 | if (Build.VERSION.SDK_INT >= 21) { 607 | if (mCustomWebViewClient != null) { 608 | return mCustomWebViewClient.shouldInterceptRequest(view, request); 609 | } 610 | else { 611 | return super.shouldInterceptRequest(view, request); 612 | } 613 | } 614 | else { 615 | return null; 616 | } 617 | } 618 | 619 | @Override 620 | public void onFormResubmission(WebView view, Message dontResend, Message resend) { 621 | if (mCustomWebViewClient != null) { 622 | mCustomWebViewClient.onFormResubmission(view, dontResend, resend); 623 | } 624 | else { 625 | super.onFormResubmission(view, dontResend, resend); 626 | } 627 | } 628 | 629 | @Override 630 | public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { 631 | if (mCustomWebViewClient != null) { 632 | mCustomWebViewClient.doUpdateVisitedHistory(view, url, isReload); 633 | } 634 | else { 635 | super.doUpdateVisitedHistory(view, url, isReload); 636 | } 637 | } 638 | 639 | @Override 640 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 641 | if (mCustomWebViewClient != null) { 642 | mCustomWebViewClient.onReceivedSslError(view, handler, error); 643 | } 644 | else { 645 | super.onReceivedSslError(view, handler, error); 646 | } 647 | } 648 | 649 | @SuppressLint("NewApi") 650 | @SuppressWarnings("all") 651 | public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { 652 | if (Build.VERSION.SDK_INT >= 21) { 653 | if (mCustomWebViewClient != null) { 654 | mCustomWebViewClient.onReceivedClientCertRequest(view, request); 655 | } 656 | else { 657 | super.onReceivedClientCertRequest(view, request); 658 | } 659 | } 660 | } 661 | 662 | @Override 663 | public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { 664 | if (mCustomWebViewClient != null) { 665 | mCustomWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm); 666 | } 667 | else { 668 | super.onReceivedHttpAuthRequest(view, handler, host, realm); 669 | } 670 | } 671 | 672 | @Override 673 | public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 674 | if (mCustomWebViewClient != null) { 675 | return mCustomWebViewClient.shouldOverrideKeyEvent(view, event); 676 | } 677 | else { 678 | return super.shouldOverrideKeyEvent(view, event); 679 | } 680 | } 681 | 682 | @Override 683 | public void onUnhandledKeyEvent(WebView view, KeyEvent event) { 684 | if (mCustomWebViewClient != null) { 685 | mCustomWebViewClient.onUnhandledKeyEvent(view, event); 686 | } 687 | else { 688 | super.onUnhandledKeyEvent(view, event); 689 | } 690 | } 691 | 692 | @SuppressLint("NewApi") 693 | @SuppressWarnings("all") 694 | public void onUnhandledInputEvent(WebView view, InputEvent event) { 695 | if (Build.VERSION.SDK_INT >= 21) { 696 | if (mCustomWebViewClient != null) { 697 | mCustomWebViewClient.onUnhandledInputEvent(view, event); 698 | } 699 | else { 700 | super.onUnhandledInputEvent(view, event); 701 | } 702 | } 703 | } 704 | 705 | @Override 706 | public void onScaleChanged(WebView view, float oldScale, float newScale) { 707 | if (mCustomWebViewClient != null) { 708 | mCustomWebViewClient.onScaleChanged(view, oldScale, newScale); 709 | } 710 | else { 711 | super.onScaleChanged(view, oldScale, newScale); 712 | } 713 | } 714 | 715 | @SuppressLint("NewApi") 716 | @SuppressWarnings("all") 717 | public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { 718 | if (Build.VERSION.SDK_INT >= 12) { 719 | if (mCustomWebViewClient != null) { 720 | mCustomWebViewClient.onReceivedLoginRequest(view, realm, account, args); 721 | } 722 | else { 723 | super.onReceivedLoginRequest(view, realm, account, args); 724 | } 725 | } 726 | } 727 | 728 | }); 729 | 730 | super.setWebChromeClient(new WebChromeClient() { 731 | 732 | // file upload callback (Android 2.2 (API level 8) -- Android 2.3 (API level 10)) (hidden method) 733 | @SuppressWarnings("unused") 734 | public void openFileChooser(ValueCallback uploadMsg) { 735 | openFileChooser(uploadMsg, null); 736 | } 737 | 738 | // file upload callback (Android 3.0 (API level 11) -- Android 4.0 (API level 15)) (hidden method) 739 | public void openFileChooser(ValueCallback uploadMsg, String acceptType) { 740 | openFileChooser(uploadMsg, acceptType, null); 741 | } 742 | 743 | // file upload callback (Android 4.1 (API level 16) -- Android 4.3 (API level 18)) (hidden method) 744 | @SuppressWarnings("unused") 745 | public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { 746 | openFileInput(uploadMsg, null, false); 747 | } 748 | 749 | // file upload callback (Android 5.0 (API level 21) -- current) (public method) 750 | @SuppressWarnings("all") 751 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { 752 | if (Build.VERSION.SDK_INT >= 21) { 753 | final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE; 754 | 755 | openFileInput(null, filePathCallback, allowMultiple); 756 | 757 | return true; 758 | } 759 | else { 760 | return false; 761 | } 762 | } 763 | 764 | @Override 765 | public void onProgressChanged(WebView view, int newProgress) { 766 | if (mCustomWebChromeClient != null) { 767 | mCustomWebChromeClient.onProgressChanged(view, newProgress); 768 | } 769 | else { 770 | super.onProgressChanged(view, newProgress); 771 | } 772 | } 773 | 774 | @Override 775 | public void onReceivedTitle(WebView view, String title) { 776 | if (mCustomWebChromeClient != null) { 777 | mCustomWebChromeClient.onReceivedTitle(view, title); 778 | } 779 | else { 780 | super.onReceivedTitle(view, title); 781 | } 782 | } 783 | 784 | @Override 785 | public void onReceivedIcon(WebView view, Bitmap icon) { 786 | if (mCustomWebChromeClient != null) { 787 | mCustomWebChromeClient.onReceivedIcon(view, icon); 788 | } 789 | else { 790 | super.onReceivedIcon(view, icon); 791 | } 792 | } 793 | 794 | @Override 795 | public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { 796 | if (mCustomWebChromeClient != null) { 797 | mCustomWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed); 798 | } 799 | else { 800 | super.onReceivedTouchIconUrl(view, url, precomposed); 801 | } 802 | } 803 | 804 | @Override 805 | public void onShowCustomView(View view, CustomViewCallback callback) { 806 | if (mCustomWebChromeClient != null) { 807 | mCustomWebChromeClient.onShowCustomView(view, callback); 808 | } 809 | else { 810 | super.onShowCustomView(view, callback); 811 | } 812 | } 813 | 814 | @SuppressLint("NewApi") 815 | @SuppressWarnings("all") 816 | public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { 817 | if (Build.VERSION.SDK_INT >= 14) { 818 | if (mCustomWebChromeClient != null) { 819 | mCustomWebChromeClient.onShowCustomView(view, requestedOrientation, callback); 820 | } 821 | else { 822 | super.onShowCustomView(view, requestedOrientation, callback); 823 | } 824 | } 825 | } 826 | 827 | @Override 828 | public void onHideCustomView() { 829 | if (mCustomWebChromeClient != null) { 830 | mCustomWebChromeClient.onHideCustomView(); 831 | } 832 | else { 833 | super.onHideCustomView(); 834 | } 835 | } 836 | 837 | @Override 838 | public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 839 | if (mCustomWebChromeClient != null) { 840 | return mCustomWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg); 841 | } 842 | else { 843 | return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); 844 | } 845 | } 846 | 847 | @Override 848 | public void onRequestFocus(WebView view) { 849 | if (mCustomWebChromeClient != null) { 850 | mCustomWebChromeClient.onRequestFocus(view); 851 | } 852 | else { 853 | super.onRequestFocus(view); 854 | } 855 | } 856 | 857 | @Override 858 | public void onCloseWindow(WebView window) { 859 | if (mCustomWebChromeClient != null) { 860 | mCustomWebChromeClient.onCloseWindow(window); 861 | } 862 | else { 863 | super.onCloseWindow(window); 864 | } 865 | } 866 | 867 | @Override 868 | public boolean onJsAlert(WebView view, String url, String message, JsResult result) { 869 | if (mCustomWebChromeClient != null) { 870 | return mCustomWebChromeClient.onJsAlert(view, url, message, result); 871 | } 872 | else { 873 | return super.onJsAlert(view, url, message, result); 874 | } 875 | } 876 | 877 | @Override 878 | public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { 879 | if (mCustomWebChromeClient != null) { 880 | return mCustomWebChromeClient.onJsConfirm(view, url, message, result); 881 | } 882 | else { 883 | return super.onJsConfirm(view, url, message, result); 884 | } 885 | } 886 | 887 | @Override 888 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 889 | if (mCustomWebChromeClient != null) { 890 | return mCustomWebChromeClient.onJsPrompt(view, url, message, defaultValue, result); 891 | } 892 | else { 893 | return super.onJsPrompt(view, url, message, defaultValue, result); 894 | } 895 | } 896 | 897 | @Override 898 | public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { 899 | if (mCustomWebChromeClient != null) { 900 | return mCustomWebChromeClient.onJsBeforeUnload(view, url, message, result); 901 | } 902 | else { 903 | return super.onJsBeforeUnload(view, url, message, result); 904 | } 905 | } 906 | 907 | @Override 908 | public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { 909 | if (mGeolocationEnabled) { 910 | callback.invoke(origin, true, false); 911 | } 912 | else { 913 | if (mCustomWebChromeClient != null) { 914 | mCustomWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); 915 | } 916 | else { 917 | super.onGeolocationPermissionsShowPrompt(origin, callback); 918 | } 919 | } 920 | } 921 | 922 | @Override 923 | public void onGeolocationPermissionsHidePrompt() { 924 | if (mCustomWebChromeClient != null) { 925 | mCustomWebChromeClient.onGeolocationPermissionsHidePrompt(); 926 | } 927 | else { 928 | super.onGeolocationPermissionsHidePrompt(); 929 | } 930 | } 931 | 932 | @SuppressLint("NewApi") 933 | @SuppressWarnings("all") 934 | public void onPermissionRequest(PermissionRequest request) { 935 | if (Build.VERSION.SDK_INT >= 21) { 936 | if (mCustomWebChromeClient != null) { 937 | mCustomWebChromeClient.onPermissionRequest(request); 938 | } 939 | else { 940 | super.onPermissionRequest(request); 941 | } 942 | } 943 | } 944 | 945 | @SuppressLint("NewApi") 946 | @SuppressWarnings("all") 947 | public void onPermissionRequestCanceled(PermissionRequest request) { 948 | if (Build.VERSION.SDK_INT >= 21) { 949 | if (mCustomWebChromeClient != null) { 950 | mCustomWebChromeClient.onPermissionRequestCanceled(request); 951 | } 952 | else { 953 | super.onPermissionRequestCanceled(request); 954 | } 955 | } 956 | } 957 | 958 | @Override 959 | public boolean onJsTimeout() { 960 | if (mCustomWebChromeClient != null) { 961 | return mCustomWebChromeClient.onJsTimeout(); 962 | } 963 | else { 964 | return super.onJsTimeout(); 965 | } 966 | } 967 | 968 | @Override 969 | public void onConsoleMessage(String message, int lineNumber, String sourceID) { 970 | if (mCustomWebChromeClient != null) { 971 | mCustomWebChromeClient.onConsoleMessage(message, lineNumber, sourceID); 972 | } 973 | else { 974 | super.onConsoleMessage(message, lineNumber, sourceID); 975 | } 976 | } 977 | 978 | @Override 979 | public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 980 | if (mCustomWebChromeClient != null) { 981 | return mCustomWebChromeClient.onConsoleMessage(consoleMessage); 982 | } 983 | else { 984 | return super.onConsoleMessage(consoleMessage); 985 | } 986 | } 987 | 988 | @Override 989 | public Bitmap getDefaultVideoPoster() { 990 | if (mCustomWebChromeClient != null) { 991 | return mCustomWebChromeClient.getDefaultVideoPoster(); 992 | } 993 | else { 994 | return super.getDefaultVideoPoster(); 995 | } 996 | } 997 | 998 | @Override 999 | public View getVideoLoadingProgressView() { 1000 | if (mCustomWebChromeClient != null) { 1001 | return mCustomWebChromeClient.getVideoLoadingProgressView(); 1002 | } 1003 | else { 1004 | return super.getVideoLoadingProgressView(); 1005 | } 1006 | } 1007 | 1008 | @Override 1009 | public void getVisitedHistory(ValueCallback callback) { 1010 | if (mCustomWebChromeClient != null) { 1011 | mCustomWebChromeClient.getVisitedHistory(callback); 1012 | } 1013 | else { 1014 | super.getVisitedHistory(callback); 1015 | } 1016 | } 1017 | 1018 | @Override 1019 | public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) { 1020 | if (mCustomWebChromeClient != null) { 1021 | mCustomWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); 1022 | } 1023 | else { 1024 | super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); 1025 | } 1026 | } 1027 | 1028 | @Override 1029 | public void onReachedMaxAppCacheSize(long requiredStorage, long quota, QuotaUpdater quotaUpdater) { 1030 | if (mCustomWebChromeClient != null) { 1031 | mCustomWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); 1032 | } 1033 | else { 1034 | super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); 1035 | } 1036 | } 1037 | 1038 | }); 1039 | 1040 | setDownloadListener(new DownloadListener() { 1041 | 1042 | @Override 1043 | public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimeType, final long contentLength) { 1044 | final String suggestedFilename = URLUtil.guessFileName(url, contentDisposition, mimeType); 1045 | 1046 | if (mListener != null) { 1047 | mListener.onDownloadRequested(url, suggestedFilename, mimeType, contentLength, contentDisposition, userAgent); 1048 | } 1049 | } 1050 | 1051 | }); 1052 | } 1053 | 1054 | @Override 1055 | public void loadUrl(final String url, Map additionalHttpHeaders) { 1056 | if (additionalHttpHeaders == null) { 1057 | additionalHttpHeaders = mHttpHeaders; 1058 | } 1059 | else if (mHttpHeaders.size() > 0) { 1060 | additionalHttpHeaders.putAll(mHttpHeaders); 1061 | } 1062 | 1063 | super.loadUrl(url, additionalHttpHeaders); 1064 | } 1065 | 1066 | @Override 1067 | public void loadUrl(final String url) { 1068 | if (mHttpHeaders.size() > 0) { 1069 | super.loadUrl(url, mHttpHeaders); 1070 | } 1071 | else { 1072 | super.loadUrl(url); 1073 | } 1074 | } 1075 | 1076 | public void loadUrl(String url, final boolean preventCaching) { 1077 | if (preventCaching) { 1078 | url = makeUrlUnique(url); 1079 | } 1080 | 1081 | loadUrl(url); 1082 | } 1083 | 1084 | public void loadUrl(String url, final boolean preventCaching, final Map additionalHttpHeaders) { 1085 | if (preventCaching) { 1086 | url = makeUrlUnique(url); 1087 | } 1088 | 1089 | loadUrl(url, additionalHttpHeaders); 1090 | } 1091 | 1092 | protected static String makeUrlUnique(final String url) { 1093 | StringBuilder unique = new StringBuilder(); 1094 | unique.append(url); 1095 | 1096 | if (url.contains("?")) { 1097 | unique.append('&'); 1098 | } 1099 | else { 1100 | if (url.lastIndexOf('/') <= 7) { 1101 | unique.append('/'); 1102 | } 1103 | unique.append('?'); 1104 | } 1105 | 1106 | unique.append(System.currentTimeMillis()); 1107 | unique.append('='); 1108 | unique.append(1); 1109 | 1110 | return unique.toString(); 1111 | } 1112 | 1113 | public boolean isPermittedUrl(final String url) { 1114 | // if the permitted hostnames have not been restricted to a specific set 1115 | if (mPermittedHostnames.size() == 0) { 1116 | // all hostnames are allowed 1117 | return true; 1118 | } 1119 | 1120 | final Uri parsedUrl = Uri.parse(url); 1121 | 1122 | // get the hostname of the URL that is to be checked 1123 | final String actualHost = parsedUrl.getHost(); 1124 | 1125 | // if the hostname could not be determined, usually because the URL has been invalid 1126 | if (actualHost == null) { 1127 | return false; 1128 | } 1129 | 1130 | // if the host contains invalid characters (e.g. a backslash) 1131 | if (!actualHost.matches("^[a-zA-Z0-9._!~*')(;:&=+$,%\\[\\]-]*$")) { 1132 | // prevent mismatches between interpretations by `Uri` and `WebView`, e.g. for `http://evil.example.com\.good.example.com/` 1133 | return false; 1134 | } 1135 | 1136 | // get the user information from the authority part of the URL that is to be checked 1137 | final String actualUserInformation = parsedUrl.getUserInfo(); 1138 | 1139 | // if the user information contains invalid characters (e.g. a backslash) 1140 | if (actualUserInformation != null && !actualUserInformation.matches("^[a-zA-Z0-9._!~*')(;:&=+$,%-]*$")) { 1141 | // prevent mismatches between interpretations by `Uri` and `WebView`, e.g. for `http://evil.example.com\@good.example.com/` 1142 | return false; 1143 | } 1144 | 1145 | // for every hostname in the set of permitted hosts 1146 | for (String expectedHost : mPermittedHostnames) { 1147 | // if the two hostnames match or if the actual host is a subdomain of the expected host 1148 | if (actualHost.equals(expectedHost) || actualHost.endsWith("." + expectedHost)) { 1149 | // the actual hostname of the URL to be checked is allowed 1150 | return true; 1151 | } 1152 | } 1153 | 1154 | // the actual hostname of the URL to be checked is not allowed since there were no matches 1155 | return false; 1156 | } 1157 | 1158 | /** 1159 | * @deprecated use `isPermittedUrl` instead 1160 | */ 1161 | protected boolean isHostnameAllowed(final String url) { 1162 | return isPermittedUrl(url); 1163 | } 1164 | 1165 | protected void setLastError() { 1166 | mLastError = System.currentTimeMillis(); 1167 | } 1168 | 1169 | protected boolean hasError() { 1170 | return (mLastError + 500) >= System.currentTimeMillis(); 1171 | } 1172 | 1173 | protected static String getLanguageIso3() { 1174 | try { 1175 | return Locale.getDefault().getISO3Language().toLowerCase(Locale.US); 1176 | } 1177 | catch (MissingResourceException e) { 1178 | return LANGUAGE_DEFAULT_ISO3; 1179 | } 1180 | } 1181 | 1182 | /** 1183 | * Provides localizations for the 25 most widely spoken languages that have a ISO 639-2/T code 1184 | * 1185 | * @return the label for the file upload prompts as a string 1186 | */ 1187 | protected String getFileUploadPromptLabel() { 1188 | try { 1189 | if (mLanguageIso3.equals("zho")) return decodeBase64("6YCJ5oup5LiA5Liq5paH5Lu2"); 1190 | else if (mLanguageIso3.equals("spa")) return decodeBase64("RWxpamEgdW4gYXJjaGl2bw=="); 1191 | else if (mLanguageIso3.equals("hin")) return decodeBase64("4KSP4KSVIOCkq+CkvOCkvuCkh+CksiDgpJrgpYHgpKjgpYfgpII="); 1192 | else if (mLanguageIso3.equals("ben")) return decodeBase64("4KaP4KaV4Kaf4Ka/IOCmq+CmvuCmh+CmsiDgpqjgpr/gprDgp43gpqzgpr7gpprgpqg="); 1193 | else if (mLanguageIso3.equals("ara")) return decodeBase64("2KfYrtiq2YrYp9ixINmF2YTZgSDZiNin2K3Yrw=="); 1194 | else if (mLanguageIso3.equals("por")) return decodeBase64("RXNjb2xoYSB1bSBhcnF1aXZv"); 1195 | else if (mLanguageIso3.equals("rus")) return decodeBase64("0JLRi9Cx0LXRgNC40YLQtSDQvtC00LjQvSDRhNCw0LnQuw=="); 1196 | else if (mLanguageIso3.equals("jpn")) return decodeBase64("MeODleOCoeOCpOODq+OCkumBuOaKnuOBl+OBpuOBj+OBoOOBleOBhA=="); 1197 | else if (mLanguageIso3.equals("pan")) return decodeBase64("4KiH4Kmx4KiVIOCoq+CovuCoh+CosiDgqJrgqYHgqKPgqYs="); 1198 | else if (mLanguageIso3.equals("deu")) return decodeBase64("V8OkaGxlIGVpbmUgRGF0ZWk="); 1199 | else if (mLanguageIso3.equals("jav")) return decodeBase64("UGlsaWggc2lqaSBiZXJrYXM="); 1200 | else if (mLanguageIso3.equals("msa")) return decodeBase64("UGlsaWggc2F0dSBmYWls"); 1201 | else if (mLanguageIso3.equals("tel")) return decodeBase64("4LCS4LCVIOCwq+CxhuCxluCwsuCxjeCwqOCxgSDgsI7gsILgsJrgsYHgsJXgsYvgsILgsKHgsL8="); 1202 | else if (mLanguageIso3.equals("vie")) return decodeBase64("Q2jhu41uIG3hu5l0IHThuq1wIHRpbg=="); 1203 | else if (mLanguageIso3.equals("kor")) return decodeBase64("7ZWY64KY7J2YIO2MjOydvOydhCDshKDtg50="); 1204 | else if (mLanguageIso3.equals("fra")) return decodeBase64("Q2hvaXNpc3NleiB1biBmaWNoaWVy"); 1205 | else if (mLanguageIso3.equals("mar")) return decodeBase64("4KSr4KS+4KSH4KSyIOCkqOCkv+CkteCkoeCkvg=="); 1206 | else if (mLanguageIso3.equals("tam")) return decodeBase64("4K6S4K6w4K+BIOCuleCvh+CuvuCuquCvjeCuquCviCDgrqTgr4fgrrDgr43grrXgr4E="); 1207 | else if (mLanguageIso3.equals("urd")) return decodeBase64("2KfbjNqpINmB2KfYptmEINmF24zauiDYs9uSINin2YbYqtiu2KfYqCDaqdix24zaug=="); 1208 | else if (mLanguageIso3.equals("fas")) return decodeBase64("2LHYpyDYp9mG2KrYrtin2Kgg2qnZhtuM2K8g24zaqSDZgdin24zZhA=="); 1209 | else if (mLanguageIso3.equals("tur")) return decodeBase64("QmlyIGRvc3lhIHNlw6dpbg=="); 1210 | else if (mLanguageIso3.equals("ita")) return decodeBase64("U2NlZ2xpIHVuIGZpbGU="); 1211 | else if (mLanguageIso3.equals("tha")) return decodeBase64("4LmA4Lil4Li34Lit4LiB4LmE4Lif4Lil4LmM4Lir4LiZ4Li24LmI4LiH"); 1212 | else if (mLanguageIso3.equals("guj")) return decodeBase64("4KqP4KqVIOCqq+CqvuCqh+CqsuCqqOCrhyDgqqrgqrjgqoLgqqY="); 1213 | } 1214 | catch (Exception ignored) { } 1215 | 1216 | // return English translation by default 1217 | return "Choose a file"; 1218 | } 1219 | 1220 | protected static String decodeBase64(final String base64) throws IllegalArgumentException, UnsupportedEncodingException { 1221 | final byte[] bytes = Base64.decode(base64, Base64.DEFAULT); 1222 | return new String(bytes, CHARSET_DEFAULT); 1223 | } 1224 | 1225 | @SuppressLint("NewApi") 1226 | protected void openFileInput(final ValueCallback fileUploadCallbackFirst, final ValueCallback fileUploadCallbackSecond, final boolean allowMultiple) { 1227 | if (mFileUploadCallbackFirst != null) { 1228 | mFileUploadCallbackFirst.onReceiveValue(null); 1229 | } 1230 | mFileUploadCallbackFirst = fileUploadCallbackFirst; 1231 | 1232 | if (mFileUploadCallbackSecond != null) { 1233 | mFileUploadCallbackSecond.onReceiveValue(null); 1234 | } 1235 | mFileUploadCallbackSecond = fileUploadCallbackSecond; 1236 | 1237 | Intent i = new Intent(Intent.ACTION_GET_CONTENT); 1238 | i.addCategory(Intent.CATEGORY_OPENABLE); 1239 | 1240 | if (allowMultiple) { 1241 | if (Build.VERSION.SDK_INT >= 18) { 1242 | i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 1243 | } 1244 | } 1245 | 1246 | i.setType(mUploadableFileTypes); 1247 | 1248 | if (mFragment != null && mFragment.get() != null && Build.VERSION.SDK_INT >= 11) { 1249 | mFragment.get().startActivityForResult(Intent.createChooser(i, getFileUploadPromptLabel()), mRequestCodeFilePicker); 1250 | } 1251 | else if (mActivity != null && mActivity.get() != null) { 1252 | mActivity.get().startActivityForResult(Intent.createChooser(i, getFileUploadPromptLabel()), mRequestCodeFilePicker); 1253 | } 1254 | } 1255 | 1256 | /** 1257 | * Returns whether file uploads can be used on the current device (generally all platform versions except for 4.4) 1258 | * 1259 | * @return whether file uploads can be used 1260 | */ 1261 | public static boolean isFileUploadAvailable() { 1262 | return isFileUploadAvailable(false); 1263 | } 1264 | 1265 | /** 1266 | * Returns whether file uploads can be used on the current device (generally all platform versions except for 4.4) 1267 | * 1268 | * On Android 4.4.3/4.4.4, file uploads may be possible but will come with a wrong MIME type 1269 | * 1270 | * @param needsCorrectMimeType whether a correct MIME type is required for file uploads or `application/octet-stream` is acceptable 1271 | * @return whether file uploads can be used 1272 | */ 1273 | public static boolean isFileUploadAvailable(final boolean needsCorrectMimeType) { 1274 | if (Build.VERSION.SDK_INT == 19) { 1275 | final String platformVersion = (Build.VERSION.RELEASE == null) ? "" : Build.VERSION.RELEASE; 1276 | 1277 | return !needsCorrectMimeType && (platformVersion.startsWith("4.4.3") || platformVersion.startsWith("4.4.4")); 1278 | } 1279 | else { 1280 | return true; 1281 | } 1282 | } 1283 | 1284 | /** 1285 | * Handles a download by loading the file from `fromUrl` and saving it to `toFilename` on the external storage 1286 | * 1287 | * This requires the two permissions `android.permission.INTERNET` and `android.permission.WRITE_EXTERNAL_STORAGE` 1288 | * 1289 | * Only supported on API level 9 (Android 2.3) and above 1290 | * 1291 | * @param context a valid `Context` reference 1292 | * @param fromUrl the URL of the file to download, e.g. the one from `AdvancedWebView.onDownloadRequested(...)` 1293 | * @param toFilename the name of the destination file where the download should be saved, e.g. `myImage.jpg` 1294 | * @return whether the download has been successfully handled or not 1295 | * @throws IllegalStateException if the storage or the target directory could not be found or accessed 1296 | */ 1297 | @SuppressLint("NewApi") 1298 | public static boolean handleDownload(final Context context, final String fromUrl, final String toFilename) { 1299 | if (Build.VERSION.SDK_INT < 9) { 1300 | throw new RuntimeException("Method requires API level 9 or above"); 1301 | } 1302 | 1303 | final Request request = new Request(Uri.parse(fromUrl)); 1304 | if (Build.VERSION.SDK_INT >= 11) { 1305 | request.allowScanningByMediaScanner(); 1306 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 1307 | } 1308 | request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, toFilename); 1309 | 1310 | final DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 1311 | try { 1312 | try { 1313 | dm.enqueue(request); 1314 | } 1315 | catch (SecurityException e) { 1316 | if (Build.VERSION.SDK_INT >= 11) { 1317 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); 1318 | } 1319 | dm.enqueue(request); 1320 | } 1321 | 1322 | return true; 1323 | } 1324 | // if the download manager app has been disabled on the device 1325 | catch (IllegalArgumentException e) { 1326 | // show the settings screen where the user can enable the download manager app again 1327 | openAppSettings(context, AdvancedWebView.PACKAGE_NAME_DOWNLOAD_MANAGER); 1328 | 1329 | return false; 1330 | } 1331 | } 1332 | 1333 | @SuppressLint("NewApi") 1334 | private static boolean openAppSettings(final Context context, final String packageName) { 1335 | if (Build.VERSION.SDK_INT < 9) { 1336 | throw new RuntimeException("Method requires API level 9 or above"); 1337 | } 1338 | 1339 | try { 1340 | final Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 1341 | intent.setData(Uri.parse("package:" + packageName)); 1342 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1343 | 1344 | context.startActivity(intent); 1345 | 1346 | return true; 1347 | } 1348 | catch (Exception e) { 1349 | return false; 1350 | } 1351 | } 1352 | 1353 | /** Wrapper for methods related to alternative browsers that have their own rendering engines */ 1354 | public static class Browsers { 1355 | 1356 | /** Package name of an alternative browser that is installed on this device */ 1357 | private static String mAlternativePackage; 1358 | 1359 | /** 1360 | * Returns whether there is an alternative browser with its own rendering engine currently installed 1361 | * 1362 | * @param context a valid `Context` reference 1363 | * @return whether there is an alternative browser or not 1364 | */ 1365 | public static boolean hasAlternative(final Context context) { 1366 | return getAlternative(context) != null; 1367 | } 1368 | 1369 | /** 1370 | * Returns the package name of an alternative browser with its own rendering engine or `null` 1371 | * 1372 | * @param context a valid `Context` reference 1373 | * @return the package name or `null` 1374 | */ 1375 | public static String getAlternative(final Context context) { 1376 | if (mAlternativePackage != null) { 1377 | return mAlternativePackage; 1378 | } 1379 | 1380 | final List alternativeBrowsers = Arrays.asList(ALTERNATIVE_BROWSERS); 1381 | final List apps = context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA); 1382 | 1383 | for (ApplicationInfo app : apps) { 1384 | if (!app.enabled) { 1385 | continue; 1386 | } 1387 | 1388 | if (alternativeBrowsers.contains(app.packageName)) { 1389 | mAlternativePackage = app.packageName; 1390 | 1391 | return app.packageName; 1392 | } 1393 | } 1394 | 1395 | return null; 1396 | } 1397 | 1398 | /** 1399 | * Opens the given URL in an alternative browser 1400 | * 1401 | * @param context a valid `Activity` reference 1402 | * @param url the URL to open 1403 | */ 1404 | public static void openUrl(final Activity context, final String url) { 1405 | openUrl(context, url, false); 1406 | } 1407 | 1408 | /** 1409 | * Opens the given URL in an alternative browser 1410 | * 1411 | * @param context a valid `Activity` reference 1412 | * @param url the URL to open 1413 | * @param withoutTransition whether to switch to the browser `Activity` without a transition 1414 | */ 1415 | public static void openUrl(final Activity context, final String url, final boolean withoutTransition) { 1416 | final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 1417 | intent.setPackage(getAlternative(context)); 1418 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1419 | 1420 | context.startActivity(intent); 1421 | 1422 | if (withoutTransition) { 1423 | context.overridePendingTransition(0, 0); 1424 | } 1425 | } 1426 | 1427 | } 1428 | 1429 | } 1430 | --------------------------------------------------------------------------------