├── 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 extends String> 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 |
--------------------------------------------------------------------------------