正在显示
23 个修改的文件
包含
2502 行增加
和
0 行删除
.gitignore
0 → 100644
build.gradle
0 → 100644
| 1 | +buildDir "${topobjdir}/gradle/build/mobile/android/geckoview_example" | ||
| 2 | + | ||
| 3 | +apply plugin: 'com.android.application' | ||
| 4 | + | ||
| 5 | +apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle" | ||
| 6 | + | ||
| 7 | +android { | ||
| 8 | + compileSdkVersion project.ext.compileSdkVersion | ||
| 9 | + | ||
| 10 | + defaultConfig { | ||
| 11 | + targetSdkVersion project.ext.targetSdkVersion | ||
| 12 | + minSdkVersion project.ext.minSdkVersion | ||
| 13 | + manifestPlaceholders = project.ext.manifestPlaceholders | ||
| 14 | + | ||
| 15 | + applicationId "org.mozilla.geckoview_example" | ||
| 16 | + versionCode 1 | ||
| 17 | + versionName "1.0" | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + compileOptions { | ||
| 21 | + sourceCompatibility JavaVersion.VERSION_1_8 | ||
| 22 | + targetCompatibility JavaVersion.VERSION_1_8 | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + buildTypes { | ||
| 26 | + release { | ||
| 27 | + minifyEnabled false | ||
| 28 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
| 29 | + } | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + project.configureProductFlavors.delegate = it | ||
| 33 | + project.configureProductFlavors() | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +dependencies { | ||
| 37 | + implementation "com.android.support:support-annotations:$support_library_version" | ||
| 38 | + implementation "com.android.support:appcompat-v7:$support_library_version" | ||
| 39 | + | ||
| 40 | + implementation project(path: ':geckoview') | ||
| 41 | +} |
gradle/wrapper/gradle-wrapper.jar
0 → 100644
不能预览此文件类型
gradle/wrapper/gradle-wrapper.properties
0 → 100644
gradlew
0 → 100644
| 1 | +#!/usr/bin/env sh | ||
| 2 | + | ||
| 3 | +############################################################################## | ||
| 4 | +## | ||
| 5 | +## Gradle start up script for UN*X | ||
| 6 | +## | ||
| 7 | +############################################################################## | ||
| 8 | + | ||
| 9 | +# Attempt to set APP_HOME | ||
| 10 | +# Resolve links: $0 may be a link | ||
| 11 | +PRG="$0" | ||
| 12 | +# Need this for relative symlinks. | ||
| 13 | +while [ -h "$PRG" ] ; do | ||
| 14 | + ls=`ls -ld "$PRG"` | ||
| 15 | + link=`expr "$ls" : '.*-> \(.*\)$'` | ||
| 16 | + if expr "$link" : '/.*' > /dev/null; then | ||
| 17 | + PRG="$link" | ||
| 18 | + else | ||
| 19 | + PRG=`dirname "$PRG"`"/$link" | ||
| 20 | + fi | ||
| 21 | +done | ||
| 22 | +SAVED="`pwd`" | ||
| 23 | +cd "`dirname \"$PRG\"`/" >/dev/null | ||
| 24 | +APP_HOME="`pwd -P`" | ||
| 25 | +cd "$SAVED" >/dev/null | ||
| 26 | + | ||
| 27 | +APP_NAME="Gradle" | ||
| 28 | +APP_BASE_NAME=`basename "$0"` | ||
| 29 | + | ||
| 30 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 31 | +DEFAULT_JVM_OPTS="" | ||
| 32 | + | ||
| 33 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| 34 | +MAX_FD="maximum" | ||
| 35 | + | ||
| 36 | +warn () { | ||
| 37 | + echo "$*" | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +die () { | ||
| 41 | + echo | ||
| 42 | + echo "$*" | ||
| 43 | + echo | ||
| 44 | + exit 1 | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +# OS specific support (must be 'true' or 'false'). | ||
| 48 | +cygwin=false | ||
| 49 | +msys=false | ||
| 50 | +darwin=false | ||
| 51 | +nonstop=false | ||
| 52 | +case "`uname`" in | ||
| 53 | + CYGWIN* ) | ||
| 54 | + cygwin=true | ||
| 55 | + ;; | ||
| 56 | + Darwin* ) | ||
| 57 | + darwin=true | ||
| 58 | + ;; | ||
| 59 | + MINGW* ) | ||
| 60 | + msys=true | ||
| 61 | + ;; | ||
| 62 | + NONSTOP* ) | ||
| 63 | + nonstop=true | ||
| 64 | + ;; | ||
| 65 | +esac | ||
| 66 | + | ||
| 67 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
| 68 | + | ||
| 69 | +# Determine the Java command to use to start the JVM. | ||
| 70 | +if [ -n "$JAVA_HOME" ] ; then | ||
| 71 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| 72 | + # IBM's JDK on AIX uses strange locations for the executables | ||
| 73 | + JAVACMD="$JAVA_HOME/jre/sh/java" | ||
| 74 | + else | ||
| 75 | + JAVACMD="$JAVA_HOME/bin/java" | ||
| 76 | + fi | ||
| 77 | + if [ ! -x "$JAVACMD" ] ; then | ||
| 78 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
| 79 | + | ||
| 80 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 81 | +location of your Java installation." | ||
| 82 | + fi | ||
| 83 | +else | ||
| 84 | + JAVACMD="java" | ||
| 85 | + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 86 | + | ||
| 87 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 88 | +location of your Java installation." | ||
| 89 | +fi | ||
| 90 | + | ||
| 91 | +# Increase the maximum file descriptors if we can. | ||
| 92 | +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | ||
| 93 | + MAX_FD_LIMIT=`ulimit -H -n` | ||
| 94 | + if [ $? -eq 0 ] ; then | ||
| 95 | + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
| 96 | + MAX_FD="$MAX_FD_LIMIT" | ||
| 97 | + fi | ||
| 98 | + ulimit -n $MAX_FD | ||
| 99 | + if [ $? -ne 0 ] ; then | ||
| 100 | + warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
| 101 | + fi | ||
| 102 | + else | ||
| 103 | + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
| 104 | + fi | ||
| 105 | +fi | ||
| 106 | + | ||
| 107 | +# For Darwin, add options to specify how the application appears in the dock | ||
| 108 | +if $darwin; then | ||
| 109 | + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
| 110 | +fi | ||
| 111 | + | ||
| 112 | +# For Cygwin, switch paths to Windows format before running java | ||
| 113 | +if $cygwin ; then | ||
| 114 | + APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
| 115 | + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
| 116 | + JAVACMD=`cygpath --unix "$JAVACMD"` | ||
| 117 | + | ||
| 118 | + # We build the pattern for arguments to be converted via cygpath | ||
| 119 | + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
| 120 | + SEP="" | ||
| 121 | + for dir in $ROOTDIRSRAW ; do | ||
| 122 | + ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
| 123 | + SEP="|" | ||
| 124 | + done | ||
| 125 | + OURCYGPATTERN="(^($ROOTDIRS))" | ||
| 126 | + # Add a user-defined pattern to the cygpath arguments | ||
| 127 | + if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
| 128 | + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
| 129 | + fi | ||
| 130 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| 131 | + i=0 | ||
| 132 | + for arg in "$@" ; do | ||
| 133 | + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
| 134 | + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
| 135 | + | ||
| 136 | + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
| 137 | + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
| 138 | + else | ||
| 139 | + eval `echo args$i`="\"$arg\"" | ||
| 140 | + fi | ||
| 141 | + i=$((i+1)) | ||
| 142 | + done | ||
| 143 | + case $i in | ||
| 144 | + (0) set -- ;; | ||
| 145 | + (1) set -- "$args0" ;; | ||
| 146 | + (2) set -- "$args0" "$args1" ;; | ||
| 147 | + (3) set -- "$args0" "$args1" "$args2" ;; | ||
| 148 | + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
| 149 | + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
| 150 | + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
| 151 | + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
| 152 | + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
| 153 | + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
| 154 | + esac | ||
| 155 | +fi | ||
| 156 | + | ||
| 157 | +# Escape application args | ||
| 158 | +save () { | ||
| 159 | + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||
| 160 | + echo " " | ||
| 161 | +} | ||
| 162 | +APP_ARGS=$(save "$@") | ||
| 163 | + | ||
| 164 | +# Collect all arguments for the java command, following the shell quoting and substitution rules | ||
| 165 | +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||
| 166 | + | ||
| 167 | +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | ||
| 168 | +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | ||
| 169 | + cd "$(dirname "$0")" | ||
| 170 | +fi | ||
| 171 | + | ||
| 172 | +exec "$JAVACMD" "$@" |
gradlew.bat
0 → 100644
| 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 | +set DIRNAME=%~dp0 | ||
| 12 | +if "%DIRNAME%" == "" set DIRNAME=. | ||
| 13 | +set APP_BASE_NAME=%~n0 | ||
| 14 | +set APP_HOME=%DIRNAME% | ||
| 15 | + | ||
| 16 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 17 | +set DEFAULT_JVM_OPTS= | ||
| 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 Windows variants | ||
| 50 | + | ||
| 51 | +if not "%OS%" == "Windows_NT" goto win9xME_args | ||
| 52 | + | ||
| 53 | +:win9xME_args | ||
| 54 | +@rem Slurp the command line arguments. | ||
| 55 | +set CMD_LINE_ARGS= | ||
| 56 | +set _SKIP=2 | ||
| 57 | + | ||
| 58 | +:win9xME_args_slurp | ||
| 59 | +if "x%~1" == "x" goto execute | ||
| 60 | + | ||
| 61 | +set CMD_LINE_ARGS=%* | ||
| 62 | + | ||
| 63 | +:execute | ||
| 64 | +@rem Setup the command line | ||
| 65 | + | ||
| 66 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||
| 67 | + | ||
| 68 | +@rem Execute Gradle | ||
| 69 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | ||
| 70 | + | ||
| 71 | +:end | ||
| 72 | +@rem End local scope for the variables with windows NT shell | ||
| 73 | +if "%ERRORLEVEL%"=="0" goto mainEnd | ||
| 74 | + | ||
| 75 | +:fail | ||
| 76 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||
| 77 | +rem the _cmd.exe /c_ return code! | ||
| 78 | +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||
| 79 | +exit /b 1 | ||
| 80 | + | ||
| 81 | +:mainEnd | ||
| 82 | +if "%OS%"=="Windows_NT" endlocal | ||
| 83 | + | ||
| 84 | +:omega |
proguard-rules.pro
0 → 100644
| 1 | +# Add project specific ProGuard rules here. | ||
| 2 | +# By default, the flags in this file are appended to flags specified | ||
| 3 | +# in /Users/nalexander/.mozbuild/android-sdk-macosx/tools/proguard/proguard-android.txt | ||
| 4 | +# You can edit the include path and order by changing the proguardFiles | ||
| 5 | +# directive in build.gradle. | ||
| 6 | +# | ||
| 7 | +# For more details, see | ||
| 8 | +# http://developer.android.com/guide/developing/tools/proguard.html | ||
| 9 | + | ||
| 10 | +# Add any project specific keep options here: | ||
| 11 | + | ||
| 12 | +# If your project uses WebView with JS, uncomment the following | ||
| 13 | +# and specify the fully qualified class name to the JavaScript interface | ||
| 14 | +# class: | ||
| 15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| 16 | +# public *; | ||
| 17 | +#} |
src/main/AndroidManifest.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | + package="org.mozilla.geckoview_example"> | ||
| 4 | + | ||
| 5 | + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||
| 6 | + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||
| 7 | + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> | ||
| 8 | + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> | ||
| 9 | + <uses-permission android:name="android.permission.RECORD_AUDIO"/> | ||
| 10 | + <uses-permission android:name="android.permission.CAMERA"/> | ||
| 11 | + | ||
| 12 | + <application | ||
| 13 | + android:allowBackup="true" | ||
| 14 | + android:label="@string/app_name" | ||
| 15 | + android:supportsRtl="true" | ||
| 16 | + android:usesCleartextTraffic="true"> | ||
| 17 | + <uses-library android:name="android.test.runner"/> | ||
| 18 | + | ||
| 19 | + <activity | ||
| 20 | + android:name=".GeckoViewActivity" | ||
| 21 | + android:label="GeckoView Example" | ||
| 22 | + android:launchMode="singleTop" | ||
| 23 | + android:theme="@style/Theme.AppCompat.Light.NoActionBar" | ||
| 24 | + android:windowSoftInputMode="stateUnspecified|adjustResize"> | ||
| 25 | + <intent-filter> | ||
| 26 | + <action android:name="android.intent.action.MAIN"/> | ||
| 27 | + | ||
| 28 | + <category android:name="android.intent.category.LAUNCHER"/> | ||
| 29 | + <category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/> | ||
| 30 | + <category android:name="android.intent.category.APP_BROWSER"/> | ||
| 31 | + <category android:name="android.intent.category.DEFAULT"/> | ||
| 32 | + </intent-filter> | ||
| 33 | + <intent-filter> | ||
| 34 | + <action android:name="android.intent.action.VIEW"/> | ||
| 35 | + | ||
| 36 | + <category android:name="android.intent.category.DEFAULT"/> | ||
| 37 | + <category android:name="android.intent.category.BROWSABLE"/> | ||
| 38 | + | ||
| 39 | + <data android:scheme="http"/> | ||
| 40 | + <data android:scheme="https"/> | ||
| 41 | + <data android:scheme="about"/> | ||
| 42 | + <data android:scheme="javascript"/> | ||
| 43 | + </intent-filter> | ||
| 44 | + </activity> | ||
| 45 | + <activity | ||
| 46 | + android:name=".SessionActivity" | ||
| 47 | + android:label="GeckoView Example" | ||
| 48 | + android:theme="@style/Theme.AppCompat.Light.NoActionBar" | ||
| 49 | + android:windowSoftInputMode="stateUnspecified|adjustResize"> | ||
| 50 | + </activity> | ||
| 51 | + | ||
| 52 | + <service | ||
| 53 | + android:name=".ExampleCrashHandler" | ||
| 54 | + android:exported="false" | ||
| 55 | + android:process=":crash"> | ||
| 56 | + </service> | ||
| 57 | + </application> | ||
| 58 | + | ||
| 59 | +</manifest> |
src/main/assets/error.html
0 → 100644
| 1 | +<!DOCTYPE html> | ||
| 2 | +<html> | ||
| 3 | +<head> | ||
| 4 | + <title>Boom!</title> | ||
| 5 | + <meta charset="utf-8"> | ||
| 6 | + <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> | ||
| 7 | + <style> | ||
| 8 | + body { | ||
| 9 | + background-color: red; | ||
| 10 | + color: white; | ||
| 11 | + font-family: sans; | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + div.container { | ||
| 15 | + width: 75%; | ||
| 16 | + margin: auto; | ||
| 17 | + text-align: center; | ||
| 18 | + } | ||
| 19 | + </style> | ||
| 20 | +</head> | ||
| 21 | +<body> | ||
| 22 | + <div class="container"> | ||
| 23 | + <h1>Boom!</h1> | ||
| 24 | + <p>Something bad happened...</p> | ||
| 25 | + <p>$ERROR</p> | ||
| 26 | + </div> | ||
| 27 | +</body> | ||
| 28 | +</html> |
| 1 | +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- | ||
| 2 | + * This Source Code Form is subject to the terms of the Mozilla Public | ||
| 3 | + * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| 4 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
| 5 | + | ||
| 6 | +package org.mozilla.geckoview_example; | ||
| 7 | + | ||
| 8 | +import android.annotation.TargetApi; | ||
| 9 | +import android.app.Activity; | ||
| 10 | +import android.app.AlertDialog; | ||
| 11 | +import android.content.ActivityNotFoundException; | ||
| 12 | +import android.content.ClipData; | ||
| 13 | +import android.content.Context; | ||
| 14 | +import android.content.DialogInterface; | ||
| 15 | +import android.content.Intent; | ||
| 16 | +import android.content.res.TypedArray; | ||
| 17 | +import android.graphics.Color; | ||
| 18 | +import android.graphics.PorterDuff; | ||
| 19 | +import android.net.Uri; | ||
| 20 | +import android.os.Build; | ||
| 21 | +import android.text.InputType; | ||
| 22 | +import android.text.format.DateFormat; | ||
| 23 | +import android.util.Log; | ||
| 24 | +import android.view.InflateException; | ||
| 25 | +import android.view.LayoutInflater; | ||
| 26 | +import android.view.View; | ||
| 27 | +import android.view.ViewGroup; | ||
| 28 | +import android.widget.AdapterView; | ||
| 29 | +import android.widget.ArrayAdapter; | ||
| 30 | +import android.widget.CheckBox; | ||
| 31 | +import android.widget.CheckedTextView; | ||
| 32 | +import android.widget.CompoundButton; | ||
| 33 | +import android.widget.DatePicker; | ||
| 34 | +import android.widget.EditText; | ||
| 35 | +import android.widget.FrameLayout; | ||
| 36 | +import android.widget.ImageView; | ||
| 37 | +import android.widget.LinearLayout; | ||
| 38 | +import android.widget.ListView; | ||
| 39 | +import android.widget.ScrollView; | ||
| 40 | +import android.widget.Spinner; | ||
| 41 | +import android.widget.TextView; | ||
| 42 | +import android.widget.TimePicker; | ||
| 43 | + | ||
| 44 | +import java.text.ParseException; | ||
| 45 | +import java.text.SimpleDateFormat; | ||
| 46 | +import java.util.ArrayList; | ||
| 47 | +import java.util.Calendar; | ||
| 48 | +import java.util.Date; | ||
| 49 | +import java.util.Locale; | ||
| 50 | + | ||
| 51 | +import org.mozilla.geckoview.AllowOrDeny; | ||
| 52 | +import org.mozilla.geckoview.GeckoResult; | ||
| 53 | +import org.mozilla.geckoview.GeckoSession; | ||
| 54 | +import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource; | ||
| 55 | + | ||
| 56 | +final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate { | ||
| 57 | + protected static final String LOGTAG = "BasicGeckoViewPrompt"; | ||
| 58 | + | ||
| 59 | + private final Activity mActivity; | ||
| 60 | + public int filePickerRequestCode = 1; | ||
| 61 | + private int mFileType; | ||
| 62 | + private FileCallback mFileCallback; | ||
| 63 | + | ||
| 64 | + public BasicGeckoViewPrompt(final Activity activity) { | ||
| 65 | + mActivity = activity; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + private AlertDialog.Builder addCheckbox(final AlertDialog.Builder builder, | ||
| 69 | + ViewGroup parent, | ||
| 70 | + final AlertCallback callback) { | ||
| 71 | + if (!callback.hasCheckbox()) { | ||
| 72 | + return builder; | ||
| 73 | + } | ||
| 74 | + final CheckBox checkbox = new CheckBox(builder.getContext()); | ||
| 75 | + if (callback.getCheckboxMessage() != null) { | ||
| 76 | + checkbox.setText(callback.getCheckboxMessage()); | ||
| 77 | + } | ||
| 78 | + checkbox.setChecked(callback.getCheckboxValue()); | ||
| 79 | + checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { | ||
| 80 | + @Override | ||
| 81 | + public void onCheckedChanged(final CompoundButton button, | ||
| 82 | + final boolean checked) { | ||
| 83 | + callback.setCheckboxValue(checked); | ||
| 84 | + } | ||
| 85 | + }); | ||
| 86 | + if (parent == null) { | ||
| 87 | + final int padding = getViewPadding(builder); | ||
| 88 | + parent = new FrameLayout(builder.getContext()); | ||
| 89 | + parent.setPadding(/* left */ padding, /* top */ 0, | ||
| 90 | + /* right */ padding, /* bottom */ 0); | ||
| 91 | + builder.setView(parent); | ||
| 92 | + } | ||
| 93 | + parent.addView(checkbox); | ||
| 94 | + return builder; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + public void onAlert(final GeckoSession session, final String title, final String msg, | ||
| 98 | + final AlertCallback callback) { | ||
| 99 | + final Activity activity = mActivity; | ||
| 100 | + if (activity == null) { | ||
| 101 | + callback.dismiss(); | ||
| 102 | + return; | ||
| 103 | + } | ||
| 104 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity) | ||
| 105 | + .setTitle(title) | ||
| 106 | + .setMessage(msg) | ||
| 107 | + .setPositiveButton(android.R.string.ok, /* onClickListener */ null); | ||
| 108 | + createStandardDialog(addCheckbox(builder, /* parent */ null, callback), | ||
| 109 | + callback).show(); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + public void onButtonPrompt(final GeckoSession session, final String title, | ||
| 113 | + final String msg, final String[] btnMsg, | ||
| 114 | + final ButtonCallback callback) { | ||
| 115 | + final Activity activity = mActivity; | ||
| 116 | + if (activity == null) { | ||
| 117 | + callback.dismiss(); | ||
| 118 | + return; | ||
| 119 | + } | ||
| 120 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity) | ||
| 121 | + .setTitle(title) | ||
| 122 | + .setMessage(msg); | ||
| 123 | + final DialogInterface.OnClickListener listener = | ||
| 124 | + new DialogInterface.OnClickListener() { | ||
| 125 | + @Override | ||
| 126 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 127 | + if (which == DialogInterface.BUTTON_POSITIVE) { | ||
| 128 | + callback.confirm(BUTTON_TYPE_POSITIVE); | ||
| 129 | + } else if (which == DialogInterface.BUTTON_NEUTRAL) { | ||
| 130 | + callback.confirm(BUTTON_TYPE_NEUTRAL); | ||
| 131 | + } else if (which == DialogInterface.BUTTON_NEGATIVE) { | ||
| 132 | + callback.confirm(BUTTON_TYPE_NEGATIVE); | ||
| 133 | + } else { | ||
| 134 | + callback.dismiss(); | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + }; | ||
| 138 | + if (btnMsg[BUTTON_TYPE_POSITIVE] != null) { | ||
| 139 | + builder.setPositiveButton(btnMsg[BUTTON_TYPE_POSITIVE], listener); | ||
| 140 | + } | ||
| 141 | + if (btnMsg[BUTTON_TYPE_NEUTRAL] != null) { | ||
| 142 | + builder.setNeutralButton(btnMsg[BUTTON_TYPE_NEUTRAL], listener); | ||
| 143 | + } | ||
| 144 | + if (btnMsg[BUTTON_TYPE_NEGATIVE] != null) { | ||
| 145 | + builder.setNegativeButton(btnMsg[BUTTON_TYPE_NEGATIVE], listener); | ||
| 146 | + } | ||
| 147 | + createStandardDialog(addCheckbox(builder, /* parent */ null, callback), | ||
| 148 | + callback).show(); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + private int getViewPadding(final AlertDialog.Builder builder) { | ||
| 152 | + final TypedArray attr = builder.getContext().obtainStyledAttributes( | ||
| 153 | + new int[] { android.R.attr.listPreferredItemPaddingLeft }); | ||
| 154 | + final int padding = attr.getDimensionPixelSize(0, 1); | ||
| 155 | + attr.recycle(); | ||
| 156 | + return padding; | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + private LinearLayout addStandardLayout(final AlertDialog.Builder builder, | ||
| 160 | + final String title, final String msg) { | ||
| 161 | + final ScrollView scrollView = new ScrollView(builder.getContext()); | ||
| 162 | + final LinearLayout container = new LinearLayout(builder.getContext()); | ||
| 163 | + final int horizontalPadding = getViewPadding(builder); | ||
| 164 | + final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0; | ||
| 165 | + container.setOrientation(LinearLayout.VERTICAL); | ||
| 166 | + container.setPadding(/* left */ horizontalPadding, /* top */ verticalPadding, | ||
| 167 | + /* right */ horizontalPadding, /* bottom */ verticalPadding); | ||
| 168 | + scrollView.addView(container); | ||
| 169 | + builder.setTitle(title) | ||
| 170 | + .setMessage(msg) | ||
| 171 | + .setView(scrollView); | ||
| 172 | + return container; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + private AlertDialog createStandardDialog(final AlertDialog.Builder builder, | ||
| 176 | + final AlertCallback callback) { | ||
| 177 | + final AlertDialog dialog = builder.create(); | ||
| 178 | + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { | ||
| 179 | + @Override | ||
| 180 | + public void onDismiss(final DialogInterface dialog) { | ||
| 181 | + callback.dismiss(); | ||
| 182 | + } | ||
| 183 | + }); | ||
| 184 | + return dialog; | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + public void onTextPrompt(final GeckoSession session, final String title, | ||
| 188 | + final String msg, final String value, | ||
| 189 | + final TextCallback callback) { | ||
| 190 | + final Activity activity = mActivity; | ||
| 191 | + if (activity == null) { | ||
| 192 | + callback.dismiss(); | ||
| 193 | + return; | ||
| 194 | + } | ||
| 195 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 196 | + final LinearLayout container = addStandardLayout(builder, title, msg); | ||
| 197 | + final EditText editText = new EditText(builder.getContext()); | ||
| 198 | + editText.setText(value); | ||
| 199 | + container.addView(editText); | ||
| 200 | + | ||
| 201 | + builder.setNegativeButton(android.R.string.cancel, /* listener */ null) | ||
| 202 | + .setPositiveButton(android.R.string.ok, | ||
| 203 | + new DialogInterface.OnClickListener() { | ||
| 204 | + @Override | ||
| 205 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 206 | + callback.confirm(editText.getText().toString()); | ||
| 207 | + } | ||
| 208 | + }); | ||
| 209 | + | ||
| 210 | + createStandardDialog(addCheckbox(builder, container, callback), callback).show(); | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + public void onAuthPrompt(final GeckoSession session, final String title, | ||
| 214 | + final String msg, final AuthOptions options, | ||
| 215 | + final AuthCallback callback) { | ||
| 216 | + final Activity activity = mActivity; | ||
| 217 | + if (activity == null) { | ||
| 218 | + callback.dismiss(); | ||
| 219 | + return; | ||
| 220 | + } | ||
| 221 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 222 | + final LinearLayout container = addStandardLayout(builder, title, msg); | ||
| 223 | + | ||
| 224 | + final int flags = options.flags; | ||
| 225 | + final int level = options.level; | ||
| 226 | + final EditText username; | ||
| 227 | + if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) { | ||
| 228 | + username = new EditText(builder.getContext()); | ||
| 229 | + username.setHint(R.string.username); | ||
| 230 | + username.setText(options.username); | ||
| 231 | + container.addView(username); | ||
| 232 | + } else { | ||
| 233 | + username = null; | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + final EditText password = new EditText(builder.getContext()); | ||
| 237 | + password.setHint(R.string.password); | ||
| 238 | + password.setText(options.password); | ||
| 239 | + password.setInputType(InputType.TYPE_CLASS_TEXT | | ||
| 240 | + InputType.TYPE_TEXT_VARIATION_PASSWORD); | ||
| 241 | + container.addView(password); | ||
| 242 | + | ||
| 243 | + if (level != AuthOptions.AUTH_LEVEL_NONE) { | ||
| 244 | + final ImageView secure = new ImageView(builder.getContext()); | ||
| 245 | + secure.setImageResource(android.R.drawable.ic_lock_lock); | ||
| 246 | + container.addView(secure); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + builder.setNegativeButton(android.R.string.cancel, /* listener */ null) | ||
| 250 | + .setPositiveButton(android.R.string.ok, | ||
| 251 | + new DialogInterface.OnClickListener() { | ||
| 252 | + @Override | ||
| 253 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 254 | + if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) { | ||
| 255 | + callback.confirm(username.getText().toString(), | ||
| 256 | + password.getText().toString()); | ||
| 257 | + } else { | ||
| 258 | + callback.confirm(password.getText().toString()); | ||
| 259 | + } | ||
| 260 | + } | ||
| 261 | + }); | ||
| 262 | + createStandardDialog(addCheckbox(builder, container, callback), callback).show(); | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + private static class ModifiableChoice { | ||
| 266 | + public boolean modifiableSelected; | ||
| 267 | + public String modifiableLabel; | ||
| 268 | + public final Choice choice; | ||
| 269 | + | ||
| 270 | + public ModifiableChoice(Choice c) { | ||
| 271 | + choice = c; | ||
| 272 | + modifiableSelected = choice.selected; | ||
| 273 | + modifiableLabel = choice.label; | ||
| 274 | + } | ||
| 275 | + } | ||
| 276 | + | ||
| 277 | + private void addChoiceItems(final int type, final ArrayAdapter<ModifiableChoice> list, | ||
| 278 | + final Choice[] items, final String indent) { | ||
| 279 | + if (type == Choice.CHOICE_TYPE_MENU) { | ||
| 280 | + for (final Choice item : items) { | ||
| 281 | + list.add(new ModifiableChoice(item)); | ||
| 282 | + } | ||
| 283 | + return; | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + for (final Choice item : items) { | ||
| 287 | + final ModifiableChoice modItem = new ModifiableChoice(item); | ||
| 288 | + | ||
| 289 | + final Choice[] children = item.items; | ||
| 290 | + | ||
| 291 | + if (indent != null && children == null) { | ||
| 292 | + modItem.modifiableLabel = indent + modItem.modifiableLabel; | ||
| 293 | + } | ||
| 294 | + list.add(modItem); | ||
| 295 | + | ||
| 296 | + if (children != null) { | ||
| 297 | + final String newIndent; | ||
| 298 | + if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MULTIPLE) { | ||
| 299 | + newIndent = (indent != null) ? indent + '\t' : "\t"; | ||
| 300 | + } else { | ||
| 301 | + newIndent = null; | ||
| 302 | + } | ||
| 303 | + addChoiceItems(type, list, children, newIndent); | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + public void onChoicePrompt(final GeckoSession session, final String title, | ||
| 309 | + final String msg, final int type, | ||
| 310 | + final Choice[] choices, final ChoiceCallback callback) { | ||
| 311 | + final Activity activity = mActivity; | ||
| 312 | + if (activity == null) { | ||
| 313 | + callback.dismiss(); | ||
| 314 | + return; | ||
| 315 | + } | ||
| 316 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 317 | + addStandardLayout(builder, title, msg); | ||
| 318 | + | ||
| 319 | + final ListView list = new ListView(builder.getContext()); | ||
| 320 | + if (type == Choice.CHOICE_TYPE_MULTIPLE) { | ||
| 321 | + list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + final ArrayAdapter<ModifiableChoice> adapter = new ArrayAdapter<ModifiableChoice>( | ||
| 325 | + builder.getContext(), android.R.layout.simple_list_item_1) { | ||
| 326 | + private static final int TYPE_MENU_ITEM = 0; | ||
| 327 | + private static final int TYPE_MENU_CHECK = 1; | ||
| 328 | + private static final int TYPE_SEPARATOR = 2; | ||
| 329 | + private static final int TYPE_GROUP = 3; | ||
| 330 | + private static final int TYPE_SINGLE = 4; | ||
| 331 | + private static final int TYPE_MULTIPLE = 5; | ||
| 332 | + private static final int TYPE_COUNT = 6; | ||
| 333 | + | ||
| 334 | + private LayoutInflater mInflater; | ||
| 335 | + private View mSeparator; | ||
| 336 | + | ||
| 337 | + @Override | ||
| 338 | + public int getViewTypeCount() { | ||
| 339 | + return TYPE_COUNT; | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + @Override | ||
| 343 | + public int getItemViewType(final int position) { | ||
| 344 | + final ModifiableChoice item = getItem(position); | ||
| 345 | + if (item.choice.separator) { | ||
| 346 | + return TYPE_SEPARATOR; | ||
| 347 | + } else if (type == Choice.CHOICE_TYPE_MENU) { | ||
| 348 | + return item.modifiableSelected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM; | ||
| 349 | + } else if (item.choice.items != null) { | ||
| 350 | + return TYPE_GROUP; | ||
| 351 | + } else if (type == Choice.CHOICE_TYPE_SINGLE) { | ||
| 352 | + return TYPE_SINGLE; | ||
| 353 | + } else if (type == Choice.CHOICE_TYPE_MULTIPLE) { | ||
| 354 | + return TYPE_MULTIPLE; | ||
| 355 | + } else { | ||
| 356 | + throw new UnsupportedOperationException(); | ||
| 357 | + } | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + @Override | ||
| 361 | + public boolean isEnabled(final int position) { | ||
| 362 | + final ModifiableChoice item = getItem(position); | ||
| 363 | + return !item.choice.separator && !item.choice.disabled && | ||
| 364 | + ((type != Choice.CHOICE_TYPE_SINGLE && type != Choice.CHOICE_TYPE_MULTIPLE) || | ||
| 365 | + item.choice.items == null); | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + @Override | ||
| 369 | + public View getView(final int position, View view, | ||
| 370 | + final ViewGroup parent) { | ||
| 371 | + final int itemType = getItemViewType(position); | ||
| 372 | + final int layoutId; | ||
| 373 | + if (itemType == TYPE_SEPARATOR) { | ||
| 374 | + if (mSeparator == null) { | ||
| 375 | + mSeparator = new View(getContext()); | ||
| 376 | + mSeparator.setLayoutParams(new ListView.LayoutParams( | ||
| 377 | + ViewGroup.LayoutParams.MATCH_PARENT, 2, itemType)); | ||
| 378 | + final TypedArray attr = getContext().obtainStyledAttributes( | ||
| 379 | + new int[] { android.R.attr.listDivider }); | ||
| 380 | + mSeparator.setBackgroundResource(attr.getResourceId(0, 0)); | ||
| 381 | + attr.recycle(); | ||
| 382 | + } | ||
| 383 | + return mSeparator; | ||
| 384 | + } else if (itemType == TYPE_MENU_ITEM) { | ||
| 385 | + layoutId = android.R.layout.simple_list_item_1; | ||
| 386 | + } else if (itemType == TYPE_MENU_CHECK) { | ||
| 387 | + layoutId = android.R.layout.simple_list_item_checked; | ||
| 388 | + } else if (itemType == TYPE_GROUP) { | ||
| 389 | + layoutId = android.R.layout.preference_category; | ||
| 390 | + } else if (itemType == TYPE_SINGLE) { | ||
| 391 | + layoutId = android.R.layout.simple_list_item_single_choice; | ||
| 392 | + } else if (itemType == TYPE_MULTIPLE) { | ||
| 393 | + layoutId = android.R.layout.simple_list_item_multiple_choice; | ||
| 394 | + } else { | ||
| 395 | + throw new UnsupportedOperationException(); | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + if (view == null) { | ||
| 399 | + if (mInflater == null) { | ||
| 400 | + mInflater = LayoutInflater.from(builder.getContext()); | ||
| 401 | + } | ||
| 402 | + view = mInflater.inflate(layoutId, parent, false); | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + final ModifiableChoice item = getItem(position); | ||
| 406 | + final TextView text = (TextView) view; | ||
| 407 | + text.setEnabled(!item.choice.disabled); | ||
| 408 | + text.setText(item.modifiableLabel); | ||
| 409 | + if (view instanceof CheckedTextView) { | ||
| 410 | + final boolean selected = item.modifiableSelected; | ||
| 411 | + if (itemType == TYPE_MULTIPLE) { | ||
| 412 | + list.setItemChecked(position, selected); | ||
| 413 | + } else { | ||
| 414 | + ((CheckedTextView) view).setChecked(selected); | ||
| 415 | + } | ||
| 416 | + } | ||
| 417 | + return view; | ||
| 418 | + } | ||
| 419 | + }; | ||
| 420 | + addChoiceItems(type, adapter, choices, /* indent */ null); | ||
| 421 | + | ||
| 422 | + list.setAdapter(adapter); | ||
| 423 | + builder.setView(list); | ||
| 424 | + | ||
| 425 | + final AlertDialog dialog; | ||
| 426 | + if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MENU) { | ||
| 427 | + dialog = createStandardDialog(builder, callback); | ||
| 428 | + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { | ||
| 429 | + @Override | ||
| 430 | + public void onItemClick(final AdapterView<?> parent, final View v, | ||
| 431 | + final int position, final long id) { | ||
| 432 | + final ModifiableChoice item = adapter.getItem(position); | ||
| 433 | + if (type == Choice.CHOICE_TYPE_MENU) { | ||
| 434 | + final Choice[] children = item.choice.items; | ||
| 435 | + if (children != null) { | ||
| 436 | + // Show sub-menu. | ||
| 437 | + dialog.setOnDismissListener(null); | ||
| 438 | + dialog.dismiss(); | ||
| 439 | + onChoicePrompt(session, item.modifiableLabel, /* msg */ null, | ||
| 440 | + type, children, callback); | ||
| 441 | + return; | ||
| 442 | + } | ||
| 443 | + } | ||
| 444 | + callback.confirm(item.choice); | ||
| 445 | + dialog.dismiss(); | ||
| 446 | + } | ||
| 447 | + }); | ||
| 448 | + } else if (type == Choice.CHOICE_TYPE_MULTIPLE) { | ||
| 449 | + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { | ||
| 450 | + @Override | ||
| 451 | + public void onItemClick(final AdapterView<?> parent, final View v, | ||
| 452 | + final int position, final long id) { | ||
| 453 | + final ModifiableChoice item = adapter.getItem(position); | ||
| 454 | + item.modifiableSelected = ((CheckedTextView) v).isChecked(); | ||
| 455 | + } | ||
| 456 | + }); | ||
| 457 | + builder.setNegativeButton(android.R.string.cancel, /* listener */ null) | ||
| 458 | + .setPositiveButton(android.R.string.ok, | ||
| 459 | + new DialogInterface.OnClickListener() { | ||
| 460 | + @Override | ||
| 461 | + public void onClick(final DialogInterface dialog, | ||
| 462 | + final int which) { | ||
| 463 | + final int len = adapter.getCount(); | ||
| 464 | + ArrayList<String> items = new ArrayList<>(len); | ||
| 465 | + for (int i = 0; i < len; i++) { | ||
| 466 | + final ModifiableChoice item = adapter.getItem(i); | ||
| 467 | + if (item.modifiableSelected) { | ||
| 468 | + items.add(item.choice.id); | ||
| 469 | + } | ||
| 470 | + } | ||
| 471 | + callback.confirm(items.toArray(new String[items.size()])); | ||
| 472 | + } | ||
| 473 | + }); | ||
| 474 | + dialog = createStandardDialog(builder, callback); | ||
| 475 | + } else { | ||
| 476 | + throw new UnsupportedOperationException(); | ||
| 477 | + } | ||
| 478 | + dialog.show(); | ||
| 479 | + } | ||
| 480 | + | ||
| 481 | + private static int parseColor(final String value, final int def) { | ||
| 482 | + try { | ||
| 483 | + return Color.parseColor(value); | ||
| 484 | + } catch (final IllegalArgumentException e) { | ||
| 485 | + return def; | ||
| 486 | + } | ||
| 487 | + } | ||
| 488 | + | ||
| 489 | + public void onColorPrompt(final GeckoSession session, final String title, | ||
| 490 | + final String value, final TextCallback callback) | ||
| 491 | + { | ||
| 492 | + final Activity activity = mActivity; | ||
| 493 | + if (activity == null) { | ||
| 494 | + callback.dismiss(); | ||
| 495 | + return; | ||
| 496 | + } | ||
| 497 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 498 | + addStandardLayout(builder, title, /* msg */ null); | ||
| 499 | + | ||
| 500 | + final int initial = parseColor(value, /* def */ 0); | ||
| 501 | + final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>( | ||
| 502 | + builder.getContext(), android.R.layout.simple_list_item_1) { | ||
| 503 | + private LayoutInflater mInflater; | ||
| 504 | + | ||
| 505 | + @Override | ||
| 506 | + public int getViewTypeCount() { | ||
| 507 | + return 2; | ||
| 508 | + } | ||
| 509 | + | ||
| 510 | + @Override | ||
| 511 | + public int getItemViewType(final int position) { | ||
| 512 | + return (getItem(position) == initial) ? 1 : 0; | ||
| 513 | + } | ||
| 514 | + | ||
| 515 | + @Override | ||
| 516 | + public View getView(final int position, View view, | ||
| 517 | + final ViewGroup parent) { | ||
| 518 | + if (mInflater == null) { | ||
| 519 | + mInflater = LayoutInflater.from(builder.getContext()); | ||
| 520 | + } | ||
| 521 | + final int color = getItem(position); | ||
| 522 | + if (view == null) { | ||
| 523 | + view = mInflater.inflate((color == initial) ? | ||
| 524 | + android.R.layout.simple_list_item_checked : | ||
| 525 | + android.R.layout.simple_list_item_1, parent, false); | ||
| 526 | + } | ||
| 527 | + view.setBackgroundResource(android.R.drawable.editbox_background); | ||
| 528 | + view.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY); | ||
| 529 | + return view; | ||
| 530 | + } | ||
| 531 | + }; | ||
| 532 | + | ||
| 533 | + adapter.addAll(0xffff4444 /* holo_red_light */, | ||
| 534 | + 0xffcc0000 /* holo_red_dark */, | ||
| 535 | + 0xffffbb33 /* holo_orange_light */, | ||
| 536 | + 0xffff8800 /* holo_orange_dark */, | ||
| 537 | + 0xff99cc00 /* holo_green_light */, | ||
| 538 | + 0xff669900 /* holo_green_dark */, | ||
| 539 | + 0xff33b5e5 /* holo_blue_light */, | ||
| 540 | + 0xff0099cc /* holo_blue_dark */, | ||
| 541 | + 0xffaa66cc /* holo_purple */, | ||
| 542 | + 0xffffffff /* white */, | ||
| 543 | + 0xffaaaaaa /* lighter_gray */, | ||
| 544 | + 0xff555555 /* darker_gray */, | ||
| 545 | + 0xff000000 /* black */); | ||
| 546 | + | ||
| 547 | + final ListView list = new ListView(builder.getContext()); | ||
| 548 | + list.setAdapter(adapter); | ||
| 549 | + builder.setView(list); | ||
| 550 | + | ||
| 551 | + final AlertDialog dialog = createStandardDialog(builder, callback); | ||
| 552 | + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { | ||
| 553 | + @Override | ||
| 554 | + public void onItemClick(final AdapterView<?> parent, final View v, | ||
| 555 | + final int position, final long id) { | ||
| 556 | + callback.confirm(String.format("#%06x", 0xffffff & adapter.getItem(position))); | ||
| 557 | + dialog.dismiss(); | ||
| 558 | + } | ||
| 559 | + }); | ||
| 560 | + dialog.show(); | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + private static Date parseDate(final SimpleDateFormat formatter, | ||
| 564 | + final String value, | ||
| 565 | + final boolean defaultToNow) { | ||
| 566 | + try { | ||
| 567 | + if (value != null && !value.isEmpty()) { | ||
| 568 | + return formatter.parse(value); | ||
| 569 | + } | ||
| 570 | + } catch (final ParseException e) { | ||
| 571 | + } | ||
| 572 | + return defaultToNow ? new Date() : null; | ||
| 573 | + } | ||
| 574 | + | ||
| 575 | + @SuppressWarnings("deprecation") | ||
| 576 | + private static void setTimePickerTime(final TimePicker picker, final Calendar cal) { | ||
| 577 | + if (Build.VERSION.SDK_INT >= 23) { | ||
| 578 | + picker.setHour(cal.get(Calendar.HOUR_OF_DAY)); | ||
| 579 | + picker.setMinute(cal.get(Calendar.MINUTE)); | ||
| 580 | + } else { | ||
| 581 | + picker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY)); | ||
| 582 | + picker.setCurrentMinute(cal.get(Calendar.MINUTE)); | ||
| 583 | + } | ||
| 584 | + } | ||
| 585 | + | ||
| 586 | + @SuppressWarnings("deprecation") | ||
| 587 | + private static void setCalendarTime(final Calendar cal, final TimePicker picker) { | ||
| 588 | + if (Build.VERSION.SDK_INT >= 23) { | ||
| 589 | + cal.set(Calendar.HOUR_OF_DAY, picker.getHour()); | ||
| 590 | + cal.set(Calendar.MINUTE, picker.getMinute()); | ||
| 591 | + } else { | ||
| 592 | + cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour()); | ||
| 593 | + cal.set(Calendar.MINUTE, picker.getCurrentMinute()); | ||
| 594 | + } | ||
| 595 | + } | ||
| 596 | + | ||
| 597 | + public void onDateTimePrompt(final GeckoSession session, final String title, | ||
| 598 | + final int type, final String value, final String min, | ||
| 599 | + final String max, final TextCallback callback) { | ||
| 600 | + final Activity activity = mActivity; | ||
| 601 | + if (activity == null) { | ||
| 602 | + callback.dismiss(); | ||
| 603 | + return; | ||
| 604 | + } | ||
| 605 | + final String format; | ||
| 606 | + if (type == DATETIME_TYPE_DATE) { | ||
| 607 | + format = "yyyy-MM-dd"; | ||
| 608 | + } else if (type == DATETIME_TYPE_MONTH) { | ||
| 609 | + format = "yyyy-MM"; | ||
| 610 | + } else if (type == DATETIME_TYPE_WEEK) { | ||
| 611 | + format = "yyyy-'W'ww"; | ||
| 612 | + } else if (type == DATETIME_TYPE_TIME) { | ||
| 613 | + format = "HH:mm"; | ||
| 614 | + } else if (type == DATETIME_TYPE_DATETIME_LOCAL) { | ||
| 615 | + format = "yyyy-MM-dd'T'HH:mm"; | ||
| 616 | + } else { | ||
| 617 | + throw new UnsupportedOperationException(); | ||
| 618 | + } | ||
| 619 | + | ||
| 620 | + final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ROOT); | ||
| 621 | + final Date minDate = parseDate(formatter, min, /* defaultToNow */ false); | ||
| 622 | + final Date maxDate = parseDate(formatter, max, /* defaultToNow */ false); | ||
| 623 | + final Date date = parseDate(formatter, value, /* defaultToNow */ true); | ||
| 624 | + final Calendar cal = formatter.getCalendar(); | ||
| 625 | + cal.setTime(date); | ||
| 626 | + | ||
| 627 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 628 | + final LayoutInflater inflater = LayoutInflater.from(builder.getContext()); | ||
| 629 | + final DatePicker datePicker; | ||
| 630 | + if (type == DATETIME_TYPE_DATE || type == DATETIME_TYPE_MONTH || | ||
| 631 | + type == DATETIME_TYPE_WEEK || type == DATETIME_TYPE_DATETIME_LOCAL) { | ||
| 632 | + final int resId = builder.getContext().getResources().getIdentifier( | ||
| 633 | + "date_picker_dialog", "layout", "android"); | ||
| 634 | + DatePicker picker = null; | ||
| 635 | + if (resId != 0) { | ||
| 636 | + try { | ||
| 637 | + picker = (DatePicker) inflater.inflate(resId, /* root */ null); | ||
| 638 | + } catch (final ClassCastException|InflateException e) { | ||
| 639 | + } | ||
| 640 | + } | ||
| 641 | + if (picker == null) { | ||
| 642 | + picker = new DatePicker(builder.getContext()); | ||
| 643 | + } | ||
| 644 | + picker.init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), | ||
| 645 | + cal.get(Calendar.DAY_OF_MONTH), /* listener */ null); | ||
| 646 | + if (minDate != null) { | ||
| 647 | + picker.setMinDate(minDate.getTime()); | ||
| 648 | + } | ||
| 649 | + if (maxDate != null) { | ||
| 650 | + picker.setMaxDate(maxDate.getTime()); | ||
| 651 | + } | ||
| 652 | + datePicker = picker; | ||
| 653 | + } else { | ||
| 654 | + datePicker = null; | ||
| 655 | + } | ||
| 656 | + | ||
| 657 | + final TimePicker timePicker; | ||
| 658 | + if (type == DATETIME_TYPE_TIME || type == DATETIME_TYPE_DATETIME_LOCAL) { | ||
| 659 | + final int resId = builder.getContext().getResources().getIdentifier( | ||
| 660 | + "time_picker_dialog", "layout", "android"); | ||
| 661 | + TimePicker picker = null; | ||
| 662 | + if (resId != 0) { | ||
| 663 | + try { | ||
| 664 | + picker = (TimePicker) inflater.inflate(resId, /* root */ null); | ||
| 665 | + } catch (final ClassCastException|InflateException e) { | ||
| 666 | + } | ||
| 667 | + } | ||
| 668 | + if (picker == null) { | ||
| 669 | + picker = new TimePicker(builder.getContext()); | ||
| 670 | + } | ||
| 671 | + setTimePickerTime(picker, cal); | ||
| 672 | + picker.setIs24HourView(DateFormat.is24HourFormat(builder.getContext())); | ||
| 673 | + timePicker = picker; | ||
| 674 | + } else { | ||
| 675 | + timePicker = null; | ||
| 676 | + } | ||
| 677 | + | ||
| 678 | + final LinearLayout container = addStandardLayout(builder, title, /* msg */ null); | ||
| 679 | + container.setPadding(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0); | ||
| 680 | + if (datePicker != null) { | ||
| 681 | + container.addView(datePicker); | ||
| 682 | + } | ||
| 683 | + if (timePicker != null) { | ||
| 684 | + container.addView(timePicker); | ||
| 685 | + } | ||
| 686 | + | ||
| 687 | + final DialogInterface.OnClickListener listener = | ||
| 688 | + new DialogInterface.OnClickListener() { | ||
| 689 | + @Override | ||
| 690 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 691 | + if (which == DialogInterface.BUTTON_NEUTRAL) { | ||
| 692 | + // Clear | ||
| 693 | + callback.confirm(""); | ||
| 694 | + return; | ||
| 695 | + } | ||
| 696 | + if (datePicker != null) { | ||
| 697 | + cal.set(datePicker.getYear(), datePicker.getMonth(), | ||
| 698 | + datePicker.getDayOfMonth()); | ||
| 699 | + } | ||
| 700 | + if (timePicker != null) { | ||
| 701 | + setCalendarTime(cal, timePicker); | ||
| 702 | + } | ||
| 703 | + callback.confirm(formatter.format(cal.getTime())); | ||
| 704 | + } | ||
| 705 | + }; | ||
| 706 | + builder.setNegativeButton(android.R.string.cancel, /* listener */ null) | ||
| 707 | + .setNeutralButton(R.string.clear_field, listener) | ||
| 708 | + .setPositiveButton(android.R.string.ok, listener); | ||
| 709 | + createStandardDialog(builder, callback).show(); | ||
| 710 | + } | ||
| 711 | + | ||
| 712 | + @TargetApi(19) | ||
| 713 | + public void onFilePrompt(GeckoSession session, String title, int type, | ||
| 714 | + String[] mimeTypes, FileCallback callback) | ||
| 715 | + { | ||
| 716 | + final Activity activity = mActivity; | ||
| 717 | + if (activity == null) { | ||
| 718 | + callback.dismiss(); | ||
| 719 | + return; | ||
| 720 | + } | ||
| 721 | + | ||
| 722 | + // Merge all given MIME types into one, using wildcard if needed. | ||
| 723 | + String mimeType = null; | ||
| 724 | + String mimeSubtype = null; | ||
| 725 | + for (final String rawType : mimeTypes) { | ||
| 726 | + final String normalizedType = rawType.trim().toLowerCase(Locale.ROOT); | ||
| 727 | + final int len = normalizedType.length(); | ||
| 728 | + int slash = normalizedType.indexOf('/'); | ||
| 729 | + if (slash < 0) { | ||
| 730 | + slash = len; | ||
| 731 | + } | ||
| 732 | + final String newType = normalizedType.substring(0, slash); | ||
| 733 | + final String newSubtype = normalizedType.substring(Math.min(slash + 1, len)); | ||
| 734 | + if (mimeType == null) { | ||
| 735 | + mimeType = newType; | ||
| 736 | + } else if (!mimeType.equals(newType)) { | ||
| 737 | + mimeType = "*"; | ||
| 738 | + } | ||
| 739 | + if (mimeSubtype == null) { | ||
| 740 | + mimeSubtype = newSubtype; | ||
| 741 | + } else if (!mimeSubtype.equals(newSubtype)) { | ||
| 742 | + mimeSubtype = "*"; | ||
| 743 | + } | ||
| 744 | + } | ||
| 745 | + | ||
| 746 | + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); | ||
| 747 | + intent.setType((mimeType != null ? mimeType : "*") + '/' + | ||
| 748 | + (mimeSubtype != null ? mimeSubtype : "*")); | ||
| 749 | + intent.addCategory(Intent.CATEGORY_OPENABLE); | ||
| 750 | + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); | ||
| 751 | + if (Build.VERSION.SDK_INT >= 18 && type == FILE_TYPE_MULTIPLE) { | ||
| 752 | + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); | ||
| 753 | + } | ||
| 754 | + if (Build.VERSION.SDK_INT >= 19 && mimeTypes.length > 0) { | ||
| 755 | + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); | ||
| 756 | + } | ||
| 757 | + | ||
| 758 | + try { | ||
| 759 | + mFileType = type; | ||
| 760 | + mFileCallback = callback; | ||
| 761 | + activity.startActivityForResult(intent, filePickerRequestCode); | ||
| 762 | + } catch (final ActivityNotFoundException e) { | ||
| 763 | + Log.e(LOGTAG, "Cannot launch activity", e); | ||
| 764 | + callback.dismiss(); | ||
| 765 | + } | ||
| 766 | + } | ||
| 767 | + | ||
| 768 | + public void onFileCallbackResult(final int resultCode, final Intent data) { | ||
| 769 | + if (mFileCallback == null) { | ||
| 770 | + return; | ||
| 771 | + } | ||
| 772 | + | ||
| 773 | + final FileCallback callback = mFileCallback; | ||
| 774 | + mFileCallback = null; | ||
| 775 | + | ||
| 776 | + if (resultCode != Activity.RESULT_OK || data == null) { | ||
| 777 | + callback.dismiss(); | ||
| 778 | + return; | ||
| 779 | + } | ||
| 780 | + | ||
| 781 | + final Uri uri = data.getData(); | ||
| 782 | + final ClipData clip = data.getClipData(); | ||
| 783 | + | ||
| 784 | + if (mFileType == FILE_TYPE_SINGLE || | ||
| 785 | + (mFileType == FILE_TYPE_MULTIPLE && clip == null)) { | ||
| 786 | + callback.confirm(mActivity, uri); | ||
| 787 | + | ||
| 788 | + } else if (mFileType == FILE_TYPE_MULTIPLE) { | ||
| 789 | + if (clip == null) { | ||
| 790 | + Log.w(LOGTAG, "No selected file"); | ||
| 791 | + callback.dismiss(); | ||
| 792 | + return; | ||
| 793 | + } | ||
| 794 | + final int count = clip.getItemCount(); | ||
| 795 | + final ArrayList<Uri> uris = new ArrayList<>(count); | ||
| 796 | + for (int i = 0; i < count; i++) { | ||
| 797 | + uris.add(clip.getItemAt(i).getUri()); | ||
| 798 | + } | ||
| 799 | + callback.confirm(mActivity, uris.toArray(new Uri[uris.size()])); | ||
| 800 | + } | ||
| 801 | + } | ||
| 802 | + | ||
| 803 | + public void onPermissionPrompt(final GeckoSession session, final String title, | ||
| 804 | + final GeckoSession.PermissionDelegate.Callback callback) { | ||
| 805 | + final Activity activity = mActivity; | ||
| 806 | + if (activity == null) { | ||
| 807 | + callback.reject(); | ||
| 808 | + return; | ||
| 809 | + } | ||
| 810 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 811 | + builder.setTitle(title) | ||
| 812 | + .setNegativeButton(android.R.string.cancel, /* onClickListener */ null) | ||
| 813 | + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | ||
| 814 | + @Override | ||
| 815 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 816 | + callback.grant(); | ||
| 817 | + } | ||
| 818 | + }); | ||
| 819 | + | ||
| 820 | + final AlertDialog dialog = builder.create(); | ||
| 821 | + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { | ||
| 822 | + @Override | ||
| 823 | + public void onDismiss(final DialogInterface dialog) { | ||
| 824 | + callback.reject(); | ||
| 825 | + } | ||
| 826 | + }); | ||
| 827 | + dialog.show(); | ||
| 828 | + } | ||
| 829 | + | ||
| 830 | + private Spinner addMediaSpinner(final Context context, final ViewGroup container, | ||
| 831 | + final MediaSource[] sources, final String[] sourceNames) { | ||
| 832 | + final ArrayAdapter<MediaSource> adapter = new ArrayAdapter<MediaSource>( | ||
| 833 | + context, android.R.layout.simple_spinner_item) { | ||
| 834 | + private View convertView(final int position, final View view) { | ||
| 835 | + if (view != null) { | ||
| 836 | + final MediaSource item = getItem(position); | ||
| 837 | + ((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name); | ||
| 838 | + } | ||
| 839 | + return view; | ||
| 840 | + } | ||
| 841 | + | ||
| 842 | + @Override | ||
| 843 | + public View getView(final int position, View view, | ||
| 844 | + final ViewGroup parent) { | ||
| 845 | + return convertView(position, super.getView(position, view, parent)); | ||
| 846 | + } | ||
| 847 | + | ||
| 848 | + @Override | ||
| 849 | + public View getDropDownView(final int position, final View view, | ||
| 850 | + final ViewGroup parent) { | ||
| 851 | + return convertView(position, super.getDropDownView(position, view, parent)); | ||
| 852 | + } | ||
| 853 | + }; | ||
| 854 | + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||
| 855 | + adapter.addAll(sources); | ||
| 856 | + | ||
| 857 | + final Spinner spinner = new Spinner(context); | ||
| 858 | + spinner.setAdapter(adapter); | ||
| 859 | + spinner.setSelection(0); | ||
| 860 | + container.addView(spinner); | ||
| 861 | + return spinner; | ||
| 862 | + } | ||
| 863 | + | ||
| 864 | + public void onMediaPrompt(final GeckoSession session, final String title, | ||
| 865 | + final MediaSource[] video, final MediaSource[] audio, | ||
| 866 | + final String[] videoNames, final String[] audioNames, | ||
| 867 | + final GeckoSession.PermissionDelegate.MediaCallback callback) { | ||
| 868 | + final Activity activity = mActivity; | ||
| 869 | + if (activity == null || (video == null && audio == null)) { | ||
| 870 | + callback.reject(); | ||
| 871 | + return; | ||
| 872 | + } | ||
| 873 | + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||
| 874 | + final LinearLayout container = addStandardLayout(builder, title, /* msg */ null); | ||
| 875 | + | ||
| 876 | + final Spinner videoSpinner; | ||
| 877 | + if (video != null) { | ||
| 878 | + videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames); | ||
| 879 | + } else { | ||
| 880 | + videoSpinner = null; | ||
| 881 | + } | ||
| 882 | + | ||
| 883 | + final Spinner audioSpinner; | ||
| 884 | + if (audio != null) { | ||
| 885 | + audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames); | ||
| 886 | + } else { | ||
| 887 | + audioSpinner = null; | ||
| 888 | + } | ||
| 889 | + | ||
| 890 | + builder.setNegativeButton(android.R.string.cancel, /* listener */ null) | ||
| 891 | + .setPositiveButton(android.R.string.ok, | ||
| 892 | + new DialogInterface.OnClickListener() { | ||
| 893 | + @Override | ||
| 894 | + public void onClick(final DialogInterface dialog, final int which) { | ||
| 895 | + final MediaSource video = (videoSpinner != null) | ||
| 896 | + ? (MediaSource) videoSpinner.getSelectedItem() : null; | ||
| 897 | + final MediaSource audio = (audioSpinner != null) | ||
| 898 | + ? (MediaSource) audioSpinner.getSelectedItem() : null; | ||
| 899 | + callback.grant(video, audio); | ||
| 900 | + } | ||
| 901 | + }); | ||
| 902 | + | ||
| 903 | + final AlertDialog dialog = builder.create(); | ||
| 904 | + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { | ||
| 905 | + @Override | ||
| 906 | + public void onDismiss(final DialogInterface dialog) { | ||
| 907 | + callback.reject(); | ||
| 908 | + } | ||
| 909 | + }); | ||
| 910 | + dialog.show(); | ||
| 911 | + } | ||
| 912 | + | ||
| 913 | + public void onMediaPrompt(final GeckoSession session, final String title, | ||
| 914 | + final MediaSource[] video, final MediaSource[] audio, | ||
| 915 | + final GeckoSession.PermissionDelegate.MediaCallback callback) { | ||
| 916 | + onMediaPrompt(session, title, video, audio, null, null, callback); | ||
| 917 | + } | ||
| 918 | + | ||
| 919 | + @Override | ||
| 920 | + public GeckoResult<AllowOrDeny> onPopupRequest(final GeckoSession session, final String targetUri) { | ||
| 921 | + return GeckoResult.fromValue(AllowOrDeny.ALLOW); | ||
| 922 | + } | ||
| 923 | +} |
| 1 | +package org.mozilla.geckoview_example; | ||
| 2 | + | ||
| 3 | +import org.mozilla.geckoview.BuildConfig; | ||
| 4 | +import org.mozilla.geckoview.CrashReporter; | ||
| 5 | +import org.mozilla.geckoview.GeckoRuntime; | ||
| 6 | + | ||
| 7 | +import android.app.Notification; | ||
| 8 | +import android.app.NotificationChannel; | ||
| 9 | +import android.app.NotificationManager; | ||
| 10 | +import android.app.PendingIntent; | ||
| 11 | +import android.app.Service; | ||
| 12 | +import android.content.Intent; | ||
| 13 | +import android.os.Build; | ||
| 14 | +import android.os.IBinder; | ||
| 15 | +import android.os.StrictMode; | ||
| 16 | +import android.support.annotation.Nullable; | ||
| 17 | +import android.support.v4.app.NotificationCompat; | ||
| 18 | +import android.util.Log; | ||
| 19 | + | ||
| 20 | +public class ExampleCrashHandler extends Service { | ||
| 21 | + private static final String LOGTAG = "ExampleCrashHandler"; | ||
| 22 | + | ||
| 23 | + private static final String CHANNEL_ID = "geckoview_example_crashes"; | ||
| 24 | + private static final int NOTIFY_ID = 42; | ||
| 25 | + | ||
| 26 | + private static final String ACTION_REPORT_CRASH = "org.mozilla.geckoview_example.ACTION_REPORT_CRASH"; | ||
| 27 | + private static final String ACTION_DISMISS = "org.mozilla.geckoview_example.ACTION_DISMISS"; | ||
| 28 | + | ||
| 29 | + private Intent mCrashIntent; | ||
| 30 | + | ||
| 31 | + public ExampleCrashHandler() { | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public int onStartCommand(Intent intent, int flags, int startId) { | ||
| 36 | + if (intent == null) { | ||
| 37 | + stopSelf(); | ||
| 38 | + return Service.START_NOT_STICKY; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + if (GeckoRuntime.ACTION_CRASHED.equals(intent.getAction())) { | ||
| 42 | + mCrashIntent = intent; | ||
| 43 | + | ||
| 44 | + Log.d(LOGTAG, "Dump File: " + | ||
| 45 | + mCrashIntent.getStringExtra(GeckoRuntime.EXTRA_MINIDUMP_PATH)); | ||
| 46 | + Log.d(LOGTAG, "Extras File: " + | ||
| 47 | + mCrashIntent.getStringExtra(GeckoRuntime.EXTRA_EXTRAS_PATH)); | ||
| 48 | + Log.d(LOGTAG, "Dump Success: " + | ||
| 49 | + mCrashIntent.getBooleanExtra(GeckoRuntime.EXTRA_MINIDUMP_SUCCESS, false)); | ||
| 50 | + Log.d(LOGTAG, "Fatal: " + | ||
| 51 | + mCrashIntent.getBooleanExtra(GeckoRuntime.EXTRA_CRASH_FATAL, false)); | ||
| 52 | + | ||
| 53 | + String id = createNotificationChannel(); | ||
| 54 | + | ||
| 55 | + PendingIntent reportIntent = PendingIntent.getService( | ||
| 56 | + this, 0, | ||
| 57 | + new Intent(ACTION_REPORT_CRASH, null, | ||
| 58 | + this, ExampleCrashHandler.class), 0); | ||
| 59 | + | ||
| 60 | + PendingIntent dismissIntent = PendingIntent.getService( | ||
| 61 | + this, 0, | ||
| 62 | + new Intent(ACTION_DISMISS, null, | ||
| 63 | + this, ExampleCrashHandler.class), 0); | ||
| 64 | + | ||
| 65 | + Notification notification = new NotificationCompat.Builder(this, id) | ||
| 66 | + .setSmallIcon(R.drawable.ic_crash) | ||
| 67 | + .setContentTitle(getResources().getString(R.string.crashed_title)) | ||
| 68 | + .setContentText(getResources().getString(R.string.crashed_text)) | ||
| 69 | + .setDefaults(Notification.DEFAULT_ALL) | ||
| 70 | + .setContentIntent(reportIntent) | ||
| 71 | + .addAction(0, getResources().getString(R.string.crashed_ignore), dismissIntent) | ||
| 72 | + .setAutoCancel(true) | ||
| 73 | + .setOngoing(false) | ||
| 74 | + .build(); | ||
| 75 | + | ||
| 76 | + startForeground(NOTIFY_ID, notification); | ||
| 77 | + } else if (ACTION_REPORT_CRASH.equals(intent.getAction())) { | ||
| 78 | + StrictMode.ThreadPolicy oldPolicy = null; | ||
| 79 | + if (BuildConfig.DEBUG) { | ||
| 80 | + oldPolicy = StrictMode.getThreadPolicy(); | ||
| 81 | + | ||
| 82 | + // We do some disk I/O and network I/O on the main thread, but it's fine. | ||
| 83 | + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(oldPolicy) | ||
| 84 | + .permitDiskReads() | ||
| 85 | + .permitDiskWrites() | ||
| 86 | + .permitNetwork() | ||
| 87 | + .build()); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + try { | ||
| 91 | + CrashReporter.sendCrashReport(this, mCrashIntent, "GeckoViewExample"); | ||
| 92 | + } catch (Exception e) { | ||
| 93 | + Log.e(LOGTAG, "Failed to send crash report", e); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + if (oldPolicy != null) { | ||
| 97 | + StrictMode.setThreadPolicy(oldPolicy); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + stopSelf(); | ||
| 101 | + } else if (ACTION_DISMISS.equals(intent.getAction())) { | ||
| 102 | + stopSelf(); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + return Service.START_NOT_STICKY; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + @Nullable | ||
| 109 | + @Override | ||
| 110 | + public IBinder onBind(Intent intent) { | ||
| 111 | + return null; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + private String createNotificationChannel() { | ||
| 115 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
| 116 | + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "GeckoView Example Crashes", NotificationManager.IMPORTANCE_LOW); | ||
| 117 | + NotificationManager notificationManager = getSystemService(NotificationManager.class); | ||
| 118 | + notificationManager.createNotificationChannel(channel); | ||
| 119 | + return CHANNEL_ID; | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + return ""; | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | +} |
| 1 | +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- | ||
| 2 | + * This Source Code Form is subject to the terms of the Mozilla Public | ||
| 3 | + * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| 4 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
| 5 | + | ||
| 6 | +package org.mozilla.geckoview_example; | ||
| 7 | + | ||
| 8 | +import org.mozilla.geckoview.AllowOrDeny; | ||
| 9 | +import org.mozilla.geckoview.BasicSelectionActionDelegate; | ||
| 10 | +import org.mozilla.geckoview.GeckoResult; | ||
| 11 | +import org.mozilla.geckoview.GeckoRuntime; | ||
| 12 | +import org.mozilla.geckoview.GeckoRuntimeSettings; | ||
| 13 | +import org.mozilla.geckoview.GeckoSession; | ||
| 14 | +import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; | ||
| 15 | +import org.mozilla.geckoview.GeckoSessionSettings; | ||
| 16 | +import org.mozilla.geckoview.GeckoView; | ||
| 17 | +import org.mozilla.geckoview.WebRequestError; | ||
| 18 | + | ||
| 19 | +import android.Manifest; | ||
| 20 | +import android.app.DownloadManager; | ||
| 21 | +import android.content.ActivityNotFoundException; | ||
| 22 | +import android.content.Intent; | ||
| 23 | +import android.content.pm.PackageManager; | ||
| 24 | +import android.net.Uri; | ||
| 25 | +import android.os.Build; | ||
| 26 | +import android.os.Bundle; | ||
| 27 | +import android.os.Environment; | ||
| 28 | +import android.os.SystemClock; | ||
| 29 | +import android.support.v4.app.ActivityCompat; | ||
| 30 | +import android.support.v4.content.ContextCompat; | ||
| 31 | +import android.support.v7.app.ActionBar; | ||
| 32 | +import android.support.v7.app.AppCompatActivity; | ||
| 33 | +import android.support.v7.widget.Toolbar; | ||
| 34 | +import android.util.Log; | ||
| 35 | +import android.view.Menu; | ||
| 36 | +import android.view.MenuInflater; | ||
| 37 | +import android.view.MenuItem; | ||
| 38 | +import android.view.View; | ||
| 39 | +import android.view.WindowManager; | ||
| 40 | +import android.widget.ProgressBar; | ||
| 41 | + | ||
| 42 | +import java.io.BufferedReader; | ||
| 43 | +import java.io.IOException; | ||
| 44 | +import java.io.InputStream; | ||
| 45 | +import java.io.InputStreamReader; | ||
| 46 | +import java.util.Arrays; | ||
| 47 | +import java.util.HashSet; | ||
| 48 | +import java.util.LinkedList; | ||
| 49 | +import java.util.Locale; | ||
| 50 | + | ||
| 51 | +public class GeckoViewActivity extends AppCompatActivity { | ||
| 52 | + private static final String LOGTAG = "GeckoViewActivity"; | ||
| 53 | + private static final String DEFAULT_URL = "about:blank"; | ||
| 54 | + private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess"; | ||
| 55 | + private static final String FULL_ACCESSIBILITY_TREE_EXTRA = "full_accessibility_tree"; | ||
| 56 | + private static final String SEARCH_URI_BASE = "https://www.google.com/search?q="; | ||
| 57 | + private static final String ACTION_SHUTDOWN = "org.mozilla.geckoview_example.SHUTDOWN"; | ||
| 58 | + private static final int REQUEST_FILE_PICKER = 1; | ||
| 59 | + private static final int REQUEST_PERMISSIONS = 2; | ||
| 60 | + private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 3; | ||
| 61 | + | ||
| 62 | + private static GeckoRuntime sGeckoRuntime; | ||
| 63 | + private GeckoSession mGeckoSession; | ||
| 64 | + private GeckoView mGeckoView; | ||
| 65 | + private boolean mUseMultiprocess; | ||
| 66 | + private boolean mFullAccessibilityTree; | ||
| 67 | + private boolean mUseTrackingProtection; | ||
| 68 | + private boolean mUsePrivateBrowsing; | ||
| 69 | + private boolean mKillProcessOnDestroy; | ||
| 70 | + | ||
| 71 | + private LocationView mLocationView; | ||
| 72 | + private String mCurrentUri; | ||
| 73 | + private boolean mCanGoBack; | ||
| 74 | + private boolean mCanGoForward; | ||
| 75 | + private boolean mFullScreen; | ||
| 76 | + | ||
| 77 | + private ProgressBar mProgressView; | ||
| 78 | + | ||
| 79 | + private LinkedList<GeckoSession.WebResponseInfo> mPendingDownloads = new LinkedList<>(); | ||
| 80 | + | ||
| 81 | + private LocationView.CommitListener mCommitListener = new LocationView.CommitListener() { | ||
| 82 | + @Override | ||
| 83 | + public void onCommit(String text) { | ||
| 84 | + if ((text.contains(".") || text.contains(":")) && !text.contains(" ")) { | ||
| 85 | + mGeckoSession.loadUri(text); | ||
| 86 | + } else { | ||
| 87 | + mGeckoSession.loadUri(SEARCH_URI_BASE + text); | ||
| 88 | + } | ||
| 89 | + mGeckoView.requestFocus(); | ||
| 90 | + } | ||
| 91 | + }; | ||
| 92 | + | ||
| 93 | + @Override | ||
| 94 | + protected void onCreate(Bundle savedInstanceState) { | ||
| 95 | + super.onCreate(savedInstanceState); | ||
| 96 | + Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + | ||
| 97 | + " - application start"); | ||
| 98 | + | ||
| 99 | + setContentView(R.layout.geckoview_activity); | ||
| 100 | + mGeckoView = (GeckoView) findViewById(R.id.gecko_view); | ||
| 101 | + | ||
| 102 | + setSupportActionBar((Toolbar)findViewById(R.id.toolbar)); | ||
| 103 | + | ||
| 104 | + mLocationView = new LocationView(this); | ||
| 105 | + mLocationView.setId(R.id.url_bar); | ||
| 106 | + getSupportActionBar().setCustomView(mLocationView, | ||
| 107 | + new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, | ||
| 108 | + ActionBar.LayoutParams.WRAP_CONTENT)); | ||
| 109 | + getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); | ||
| 110 | + | ||
| 111 | + mUseMultiprocess = getIntent().getBooleanExtra(USE_MULTIPROCESS_EXTRA, true); | ||
| 112 | + mFullAccessibilityTree = getIntent().getBooleanExtra(FULL_ACCESSIBILITY_TREE_EXTRA, false); | ||
| 113 | + mProgressView = (ProgressBar) findViewById(R.id.page_progress); | ||
| 114 | + | ||
| 115 | + if (sGeckoRuntime == null) { | ||
| 116 | + final GeckoRuntimeSettings.Builder runtimeSettingsBuilder = | ||
| 117 | + new GeckoRuntimeSettings.Builder(); | ||
| 118 | + | ||
| 119 | + if (BuildConfig.DEBUG) { | ||
| 120 | + // In debug builds, we want to load JavaScript resources fresh with | ||
| 121 | + // each build. | ||
| 122 | + runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" }); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + final Bundle extras = getIntent().getExtras(); | ||
| 126 | + if (extras != null) { | ||
| 127 | + runtimeSettingsBuilder.extras(extras); | ||
| 128 | + } | ||
| 129 | + runtimeSettingsBuilder | ||
| 130 | + .useContentProcessHint(mUseMultiprocess) | ||
| 131 | + .remoteDebuggingEnabled(true) | ||
| 132 | + .consoleOutput(true) | ||
| 133 | + .trackingProtectionCategories(TrackingProtectionDelegate.CATEGORY_ALL) | ||
| 134 | + .crashHandler(ExampleCrashHandler.class); | ||
| 135 | + | ||
| 136 | + sGeckoRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build()); | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + if(savedInstanceState == null) { | ||
| 140 | + mGeckoSession = (GeckoSession)getIntent().getParcelableExtra("session"); | ||
| 141 | + if (mGeckoSession != null) { | ||
| 142 | + connectSession(mGeckoSession); | ||
| 143 | + | ||
| 144 | + if (!mGeckoSession.isOpen()) { | ||
| 145 | + mGeckoSession.open(sGeckoRuntime); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + mUseMultiprocess = mGeckoSession.getSettings().getUseMultiprocess(); | ||
| 149 | + mFullAccessibilityTree = mGeckoSession.getSettings().getFullAccessibilityTree(); | ||
| 150 | + | ||
| 151 | + mGeckoView.setSession(mGeckoSession); | ||
| 152 | + } else { | ||
| 153 | + mGeckoSession = createSession(); | ||
| 154 | + mGeckoView.setSession(mGeckoSession, sGeckoRuntime); | ||
| 155 | + | ||
| 156 | + loadFromIntent(getIntent()); | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + mLocationView.setCommitListener(mCommitListener); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + private GeckoSession createSession() { | ||
| 164 | + GeckoSession session = new GeckoSession(new GeckoSessionSettings.Builder() | ||
| 165 | + .useMultiprocess(mUseMultiprocess) | ||
| 166 | + .usePrivateMode(mUsePrivateBrowsing) | ||
| 167 | + .useTrackingProtection(mUseTrackingProtection) | ||
| 168 | + .fullAccessibilityTree(mFullAccessibilityTree) | ||
| 169 | + .build()); | ||
| 170 | + | ||
| 171 | + connectSession(session); | ||
| 172 | + | ||
| 173 | + return session; | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + private void connectSession(GeckoSession session) { | ||
| 177 | + session.setContentDelegate(new ExampleContentDelegate()); | ||
| 178 | + session.setHistoryDelegate(new ExampleHistoryDelegate()); | ||
| 179 | + final ExampleTrackingProtectionDelegate tp = new ExampleTrackingProtectionDelegate(); | ||
| 180 | + session.setTrackingProtectionDelegate(tp); | ||
| 181 | + session.setProgressDelegate(new ExampleProgressDelegate(tp)); | ||
| 182 | + session.setNavigationDelegate(new ExampleNavigationDelegate()); | ||
| 183 | + | ||
| 184 | + final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt(this); | ||
| 185 | + prompt.filePickerRequestCode = REQUEST_FILE_PICKER; | ||
| 186 | + session.setPromptDelegate(prompt); | ||
| 187 | + | ||
| 188 | + final ExamplePermissionDelegate permission = new ExamplePermissionDelegate(); | ||
| 189 | + permission.androidPermissionRequestCode = REQUEST_PERMISSIONS; | ||
| 190 | + session.setPermissionDelegate(permission); | ||
| 191 | + | ||
| 192 | + session.setSelectionActionDelegate(new BasicSelectionActionDelegate(this)); | ||
| 193 | + | ||
| 194 | + updateTrackingProtection(session); | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + private void recreateSession() { | ||
| 198 | + if(mGeckoSession != null) { | ||
| 199 | + mGeckoSession.close(); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + mGeckoSession = createSession(); | ||
| 203 | + mGeckoSession.open(sGeckoRuntime); | ||
| 204 | + mGeckoView.setSession(mGeckoSession); | ||
| 205 | + mGeckoSession.loadUri(mCurrentUri != null ? mCurrentUri : DEFAULT_URL); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + @Override | ||
| 209 | + public void onRestoreInstanceState(Bundle savedInstanceState) { | ||
| 210 | + super.onRestoreInstanceState(savedInstanceState); | ||
| 211 | + if(savedInstanceState != null) { | ||
| 212 | + mGeckoSession = mGeckoView.getSession(); | ||
| 213 | + } else { | ||
| 214 | + recreateSession(); | ||
| 215 | + } | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + private void updateTrackingProtection(GeckoSession session) { | ||
| 219 | + session.getSettings().setUseTrackingProtection(mUseTrackingProtection); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + @Override | ||
| 223 | + public void onBackPressed() { | ||
| 224 | + if (mFullScreen) { | ||
| 225 | + mGeckoSession.exitFullScreen(); | ||
| 226 | + return; | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + if (mCanGoBack && mGeckoSession != null) { | ||
| 230 | + mGeckoSession.goBack(); | ||
| 231 | + return; | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + super.onBackPressed(); | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + @Override | ||
| 238 | + public boolean onCreateOptionsMenu(Menu menu) { | ||
| 239 | + MenuInflater inflater = getMenuInflater(); | ||
| 240 | + inflater.inflate(R.menu.actions, menu); | ||
| 241 | + return true; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + @Override | ||
| 245 | + public boolean onPrepareOptionsMenu(Menu menu) { | ||
| 246 | + menu.findItem(R.id.action_e10s).setChecked(mUseMultiprocess); | ||
| 247 | + menu.findItem(R.id.action_tp).setChecked(mUseTrackingProtection); | ||
| 248 | + menu.findItem(R.id.action_pb).setChecked(mUsePrivateBrowsing); | ||
| 249 | + menu.findItem(R.id.action_forward).setEnabled(mCanGoForward); | ||
| 250 | + return true; | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + @Override | ||
| 254 | + public boolean onOptionsItemSelected(MenuItem item) { | ||
| 255 | + switch (item.getItemId()) { | ||
| 256 | + case R.id.action_reload: | ||
| 257 | + mGeckoSession.reload(); | ||
| 258 | + break; | ||
| 259 | + case R.id.action_forward: | ||
| 260 | + mGeckoSession.goForward(); | ||
| 261 | + break; | ||
| 262 | + case R.id.action_e10s: | ||
| 263 | + mUseMultiprocess = !mUseMultiprocess; | ||
| 264 | + recreateSession(); | ||
| 265 | + break; | ||
| 266 | + case R.id.action_tp: | ||
| 267 | + mUseTrackingProtection = !mUseTrackingProtection; | ||
| 268 | + updateTrackingProtection(mGeckoSession); | ||
| 269 | + mGeckoSession.reload(); | ||
| 270 | + break; | ||
| 271 | + case R.id.action_pb: | ||
| 272 | + mUsePrivateBrowsing = !mUsePrivateBrowsing; | ||
| 273 | + recreateSession(); | ||
| 274 | + break; | ||
| 275 | + default: | ||
| 276 | + return super.onOptionsItemSelected(item); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + return true; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + @Override | ||
| 283 | + public void onDestroy() { | ||
| 284 | + if (mKillProcessOnDestroy) { | ||
| 285 | + android.os.Process.killProcess(android.os.Process.myPid()); | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + super.onDestroy(); | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + protected void onNewIntent(final Intent intent) { | ||
| 292 | + super.onNewIntent(intent); | ||
| 293 | + | ||
| 294 | + if (ACTION_SHUTDOWN.equals(intent.getAction())) { | ||
| 295 | + mKillProcessOnDestroy = true; | ||
| 296 | + if (sGeckoRuntime != null) { | ||
| 297 | + sGeckoRuntime.shutdown(); | ||
| 298 | + } | ||
| 299 | + finish(); | ||
| 300 | + return; | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + setIntent(intent); | ||
| 304 | + | ||
| 305 | + if (intent.getData() != null) { | ||
| 306 | + loadFromIntent(intent); | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + | ||
| 311 | + private void loadFromIntent(final Intent intent) { | ||
| 312 | + final Uri uri = intent.getData(); | ||
| 313 | + mGeckoSession.loadUri(uri != null ? uri.toString() : DEFAULT_URL); | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + @Override | ||
| 317 | + protected void onActivityResult(final int requestCode, final int resultCode, | ||
| 318 | + final Intent data) { | ||
| 319 | + if (requestCode == REQUEST_FILE_PICKER) { | ||
| 320 | + final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt) | ||
| 321 | + mGeckoSession.getPromptDelegate(); | ||
| 322 | + prompt.onFileCallbackResult(resultCode, data); | ||
| 323 | + } else { | ||
| 324 | + super.onActivityResult(requestCode, resultCode, data); | ||
| 325 | + } | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + @Override | ||
| 329 | + public void onRequestPermissionsResult(final int requestCode, | ||
| 330 | + final String[] permissions, | ||
| 331 | + final int[] grantResults) { | ||
| 332 | + if (requestCode == REQUEST_PERMISSIONS) { | ||
| 333 | + final ExamplePermissionDelegate permission = (ExamplePermissionDelegate) | ||
| 334 | + mGeckoSession.getPermissionDelegate(); | ||
| 335 | + permission.onRequestPermissionsResult(permissions, grantResults); | ||
| 336 | + } else if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE && | ||
| 337 | + grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||
| 338 | + continueDownloads(); | ||
| 339 | + } else { | ||
| 340 | + super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + private void continueDownloads() { | ||
| 345 | + LinkedList<GeckoSession.WebResponseInfo> downloads = mPendingDownloads; | ||
| 346 | + mPendingDownloads = new LinkedList<>(); | ||
| 347 | + | ||
| 348 | + for (GeckoSession.WebResponseInfo response : downloads) { | ||
| 349 | + downloadFile(response); | ||
| 350 | + } | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + private void downloadFile(GeckoSession.WebResponseInfo response) { | ||
| 354 | + mGeckoSession | ||
| 355 | + .getUserAgent() | ||
| 356 | + .then(new GeckoResult.OnValueListener<String, Void>() { | ||
| 357 | + @Override | ||
| 358 | + public GeckoResult<Void> onValue(String userAgent) throws Throwable { | ||
| 359 | + downloadFile(response, userAgent); | ||
| 360 | + return null; | ||
| 361 | + } | ||
| 362 | + }, new GeckoResult.OnExceptionListener<Void>() { | ||
| 363 | + @Override | ||
| 364 | + public GeckoResult<Void> onException(Throwable exception) throws Throwable { | ||
| 365 | + // getUserAgent() cannot fail. | ||
| 366 | + throw new IllegalStateException("Could not get UserAgent string."); | ||
| 367 | + } | ||
| 368 | + }); | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + private void downloadFile(GeckoSession.WebResponseInfo response, String userAgent) { | ||
| 372 | + if (ContextCompat.checkSelfPermission(GeckoViewActivity.this, | ||
| 373 | + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { | ||
| 374 | + mPendingDownloads.add(response); | ||
| 375 | + ActivityCompat.requestPermissions(GeckoViewActivity.this, | ||
| 376 | + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, | ||
| 377 | + REQUEST_WRITE_EXTERNAL_STORAGE); | ||
| 378 | + return; | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + final Uri uri = Uri.parse(response.uri); | ||
| 382 | + final String filename = response.filename != null ? response.filename : uri.getLastPathSegment(); | ||
| 383 | + | ||
| 384 | + DownloadManager manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); | ||
| 385 | + DownloadManager.Request req = new DownloadManager.Request(uri); | ||
| 386 | + req.setMimeType(response.contentType); | ||
| 387 | + req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); | ||
| 388 | + req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); | ||
| 389 | + req.addRequestHeader("User-Agent", userAgent); | ||
| 390 | + manager.enqueue(req); | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + private String mErrorTemplate; | ||
| 394 | + private String createErrorPage(final String error) { | ||
| 395 | + if (mErrorTemplate == null) { | ||
| 396 | + InputStream stream = null; | ||
| 397 | + BufferedReader reader = null; | ||
| 398 | + StringBuilder builder = new StringBuilder(); | ||
| 399 | + try { | ||
| 400 | + stream = getResources().getAssets().open("error.html"); | ||
| 401 | + reader = new BufferedReader(new InputStreamReader(stream)); | ||
| 402 | + | ||
| 403 | + String line; | ||
| 404 | + while ((line = reader.readLine()) != null) { | ||
| 405 | + builder.append(line); | ||
| 406 | + builder.append("\n"); | ||
| 407 | + } | ||
| 408 | + | ||
| 409 | + mErrorTemplate = builder.toString(); | ||
| 410 | + } catch (IOException e) { | ||
| 411 | + Log.d(LOGTAG, "Failed to open error page template", e); | ||
| 412 | + return null; | ||
| 413 | + } finally { | ||
| 414 | + if (stream != null) { | ||
| 415 | + try { | ||
| 416 | + stream.close(); | ||
| 417 | + } catch (IOException e) { | ||
| 418 | + Log.e(LOGTAG, "Failed to close error page template stream", e); | ||
| 419 | + } | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + if (reader != null) { | ||
| 423 | + try { | ||
| 424 | + reader.close(); | ||
| 425 | + } catch (IOException e) { | ||
| 426 | + Log.e(LOGTAG, "Failed to close error page template reader", e); | ||
| 427 | + } | ||
| 428 | + } | ||
| 429 | + } | ||
| 430 | + } | ||
| 431 | + | ||
| 432 | + return mErrorTemplate.replace("$ERROR", error); | ||
| 433 | + } | ||
| 434 | + | ||
| 435 | + private class ExampleHistoryDelegate implements GeckoSession.HistoryDelegate { | ||
| 436 | + private final HashSet<String> mVisitedURLs; | ||
| 437 | + | ||
| 438 | + private ExampleHistoryDelegate() { | ||
| 439 | + mVisitedURLs = new HashSet<String>(); | ||
| 440 | + } | ||
| 441 | + | ||
| 442 | + @Override | ||
| 443 | + public GeckoResult<Boolean> onVisited(GeckoSession session, String url, | ||
| 444 | + String lastVisitedURL, int flags) { | ||
| 445 | + Log.i(LOGTAG, "Visited URL: " + url); | ||
| 446 | + | ||
| 447 | + mVisitedURLs.add(url); | ||
| 448 | + return GeckoResult.fromValue(true); | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + @Override | ||
| 452 | + public GeckoResult<boolean[]> getVisited(GeckoSession session, String[] urls) { | ||
| 453 | + boolean[] visited = new boolean[urls.length]; | ||
| 454 | + for (int i = 0; i < urls.length; i++) { | ||
| 455 | + visited[i] = mVisitedURLs.contains(urls[i]); | ||
| 456 | + } | ||
| 457 | + return GeckoResult.fromValue(visited); | ||
| 458 | + } | ||
| 459 | + } | ||
| 460 | + | ||
| 461 | + private class ExampleContentDelegate implements GeckoSession.ContentDelegate { | ||
| 462 | + @Override | ||
| 463 | + public void onTitleChange(GeckoSession session, String title) { | ||
| 464 | + Log.i(LOGTAG, "Content title changed to " + title); | ||
| 465 | + } | ||
| 466 | + | ||
| 467 | + @Override | ||
| 468 | + public void onFullScreen(final GeckoSession session, final boolean fullScreen) { | ||
| 469 | + getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, | ||
| 470 | + WindowManager.LayoutParams.FLAG_FULLSCREEN); | ||
| 471 | + mFullScreen = fullScreen; | ||
| 472 | + if (fullScreen) { | ||
| 473 | + getSupportActionBar().hide(); | ||
| 474 | + } else { | ||
| 475 | + getSupportActionBar().show(); | ||
| 476 | + } | ||
| 477 | + } | ||
| 478 | + | ||
| 479 | + @Override | ||
| 480 | + public void onFocusRequest(final GeckoSession session) { | ||
| 481 | + Log.i(LOGTAG, "Content requesting focus"); | ||
| 482 | + } | ||
| 483 | + | ||
| 484 | + @Override | ||
| 485 | + public void onCloseRequest(final GeckoSession session) { | ||
| 486 | + if (session == mGeckoSession) { | ||
| 487 | + finish(); | ||
| 488 | + } | ||
| 489 | + } | ||
| 490 | + | ||
| 491 | + @Override | ||
| 492 | + public void onContextMenu(final GeckoSession session, | ||
| 493 | + int screenX, int screenY, | ||
| 494 | + final ContextElement element) { | ||
| 495 | + Log.d(LOGTAG, "onContextMenu screenX=" + screenX + | ||
| 496 | + " screenY=" + screenY + | ||
| 497 | + " type=" + element.type + | ||
| 498 | + " linkUri=" + element.linkUri + | ||
| 499 | + " title=" + element.title + | ||
| 500 | + " alt=" + element.altText + | ||
| 501 | + " srcUri=" + element.srcUri); | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + @Override | ||
| 505 | + public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo response) { | ||
| 506 | + try { | ||
| 507 | + Intent intent = new Intent(Intent.ACTION_VIEW); | ||
| 508 | + intent.setDataAndTypeAndNormalize(Uri.parse(response.uri), response.contentType); | ||
| 509 | + startActivity(intent); | ||
| 510 | + } catch (ActivityNotFoundException e) { | ||
| 511 | + downloadFile(response); | ||
| 512 | + } | ||
| 513 | + } | ||
| 514 | + | ||
| 515 | + @Override | ||
| 516 | + public void onCrash(GeckoSession session) { | ||
| 517 | + Log.e(LOGTAG, "Crashed, reopening session"); | ||
| 518 | + session.open(sGeckoRuntime); | ||
| 519 | + session.loadUri(DEFAULT_URL); | ||
| 520 | + } | ||
| 521 | + | ||
| 522 | + @Override | ||
| 523 | + public void onFirstComposite(final GeckoSession session) { | ||
| 524 | + Log.d(LOGTAG, "onFirstComposite"); | ||
| 525 | + } | ||
| 526 | + } | ||
| 527 | + | ||
| 528 | + private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate { | ||
| 529 | + private ExampleTrackingProtectionDelegate mTp; | ||
| 530 | + | ||
| 531 | + private ExampleProgressDelegate(final ExampleTrackingProtectionDelegate tp) { | ||
| 532 | + mTp = tp; | ||
| 533 | + } | ||
| 534 | + | ||
| 535 | + @Override | ||
| 536 | + public void onPageStart(GeckoSession session, String url) { | ||
| 537 | + Log.i(LOGTAG, "Starting to load page at " + url); | ||
| 538 | + Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + | ||
| 539 | + " - page load start"); | ||
| 540 | + mTp.clearCounters(); | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + @Override | ||
| 544 | + public void onPageStop(GeckoSession session, boolean success) { | ||
| 545 | + Log.i(LOGTAG, "Stopping page load " + (success ? "successfully" : "unsuccessfully")); | ||
| 546 | + Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + | ||
| 547 | + " - page load stop"); | ||
| 548 | + mTp.logCounters(); | ||
| 549 | + } | ||
| 550 | + | ||
| 551 | + @Override | ||
| 552 | + public void onProgressChange(GeckoSession session, int progress) { | ||
| 553 | + Log.i(LOGTAG, "onProgressChange " + progress); | ||
| 554 | + | ||
| 555 | + mProgressView.setProgress(progress); | ||
| 556 | + | ||
| 557 | + if (progress > 0 && progress < 100) { | ||
| 558 | + mProgressView.setVisibility(View.VISIBLE); | ||
| 559 | + } else { | ||
| 560 | + mProgressView.setVisibility(View.GONE); | ||
| 561 | + } | ||
| 562 | + } | ||
| 563 | + | ||
| 564 | + @Override | ||
| 565 | + public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) { | ||
| 566 | + Log.i(LOGTAG, "Security status changed to " + securityInfo.securityMode); | ||
| 567 | + } | ||
| 568 | + } | ||
| 569 | + | ||
| 570 | + private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate { | ||
| 571 | + | ||
| 572 | + public int androidPermissionRequestCode = 1; | ||
| 573 | + private Callback mCallback; | ||
| 574 | + | ||
| 575 | + public void onRequestPermissionsResult(final String[] permissions, | ||
| 576 | + final int[] grantResults) { | ||
| 577 | + if (mCallback == null) { | ||
| 578 | + return; | ||
| 579 | + } | ||
| 580 | + | ||
| 581 | + final Callback cb = mCallback; | ||
| 582 | + mCallback = null; | ||
| 583 | + for (final int result : grantResults) { | ||
| 584 | + if (result != PackageManager.PERMISSION_GRANTED) { | ||
| 585 | + // At least one permission was not granted. | ||
| 586 | + cb.reject(); | ||
| 587 | + return; | ||
| 588 | + } | ||
| 589 | + } | ||
| 590 | + cb.grant(); | ||
| 591 | + } | ||
| 592 | + | ||
| 593 | + @Override | ||
| 594 | + public void onAndroidPermissionsRequest(final GeckoSession session, final String[] permissions, | ||
| 595 | + final Callback callback) { | ||
| 596 | + if (Build.VERSION.SDK_INT >= 23) { | ||
| 597 | + // requestPermissions was introduced in API 23. | ||
| 598 | + mCallback = callback; | ||
| 599 | + requestPermissions(permissions, androidPermissionRequestCode); | ||
| 600 | + } else { | ||
| 601 | + callback.grant(); | ||
| 602 | + } | ||
| 603 | + } | ||
| 604 | + | ||
| 605 | + @Override | ||
| 606 | + public void onContentPermissionRequest(final GeckoSession session, final String uri, | ||
| 607 | + final int type, final Callback callback) { | ||
| 608 | + final int resId; | ||
| 609 | + if (PERMISSION_GEOLOCATION == type) { | ||
| 610 | + resId = R.string.request_geolocation; | ||
| 611 | + } else if (PERMISSION_DESKTOP_NOTIFICATION == type) { | ||
| 612 | + resId = R.string.request_notification; | ||
| 613 | + } else if (PERMISSION_AUTOPLAY_MEDIA == type) { | ||
| 614 | + resId = R.string.request_autoplay; | ||
| 615 | + } else { | ||
| 616 | + Log.w(LOGTAG, "Unknown permission: " + type); | ||
| 617 | + callback.reject(); | ||
| 618 | + return; | ||
| 619 | + } | ||
| 620 | + | ||
| 621 | + final String title = getString(resId, Uri.parse(uri).getAuthority()); | ||
| 622 | + final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt) | ||
| 623 | + mGeckoSession.getPromptDelegate(); | ||
| 624 | + prompt.onPermissionPrompt(session, title, callback); | ||
| 625 | + } | ||
| 626 | + | ||
| 627 | + private String[] normalizeMediaName(final MediaSource[] sources) { | ||
| 628 | + if (sources == null) { | ||
| 629 | + return null; | ||
| 630 | + } | ||
| 631 | + | ||
| 632 | + String[] res = new String[sources.length]; | ||
| 633 | + for (int i = 0; i < sources.length; i++) { | ||
| 634 | + final int mediaSource = sources[i].source; | ||
| 635 | + final String name = sources[i].name; | ||
| 636 | + if (MediaSource.SOURCE_CAMERA == mediaSource) { | ||
| 637 | + if (name.toLowerCase(Locale.ENGLISH).contains("front")) { | ||
| 638 | + res[i] = getString(R.string.media_front_camera); | ||
| 639 | + } else { | ||
| 640 | + res[i] = getString(R.string.media_back_camera); | ||
| 641 | + } | ||
| 642 | + } else if (!name.isEmpty()) { | ||
| 643 | + res[i] = name; | ||
| 644 | + } else if (MediaSource.SOURCE_MICROPHONE == mediaSource) { | ||
| 645 | + res[i] = getString(R.string.media_microphone); | ||
| 646 | + } else { | ||
| 647 | + res[i] = getString(R.string.media_other); | ||
| 648 | + } | ||
| 649 | + } | ||
| 650 | + | ||
| 651 | + return res; | ||
| 652 | + } | ||
| 653 | + | ||
| 654 | + @Override | ||
| 655 | + public void onMediaPermissionRequest(final GeckoSession session, final String uri, | ||
| 656 | + final MediaSource[] video, final MediaSource[] audio, | ||
| 657 | + final MediaCallback callback) { | ||
| 658 | + final String host = Uri.parse(uri).getAuthority(); | ||
| 659 | + final String title; | ||
| 660 | + if (audio == null) { | ||
| 661 | + title = getString(R.string.request_video, host); | ||
| 662 | + } else if (video == null) { | ||
| 663 | + title = getString(R.string.request_audio, host); | ||
| 664 | + } else { | ||
| 665 | + title = getString(R.string.request_media, host); | ||
| 666 | + } | ||
| 667 | + | ||
| 668 | + String[] videoNames = normalizeMediaName(video); | ||
| 669 | + String[] audioNames = normalizeMediaName(audio); | ||
| 670 | + | ||
| 671 | + final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt) | ||
| 672 | + mGeckoSession.getPromptDelegate(); | ||
| 673 | + prompt.onMediaPrompt(session, title, video, audio, videoNames, audioNames, callback); | ||
| 674 | + } | ||
| 675 | + } | ||
| 676 | + | ||
| 677 | + private class ExampleNavigationDelegate implements GeckoSession.NavigationDelegate { | ||
| 678 | + @Override | ||
| 679 | + public void onLocationChange(GeckoSession session, final String url) { | ||
| 680 | + mLocationView.setText(url); | ||
| 681 | + mCurrentUri = url; | ||
| 682 | + } | ||
| 683 | + | ||
| 684 | + @Override | ||
| 685 | + public void onCanGoBack(GeckoSession session, boolean canGoBack) { | ||
| 686 | + mCanGoBack = canGoBack; | ||
| 687 | + } | ||
| 688 | + | ||
| 689 | + @Override | ||
| 690 | + public void onCanGoForward(GeckoSession session, boolean canGoForward) { | ||
| 691 | + mCanGoForward = canGoForward; | ||
| 692 | + } | ||
| 693 | + | ||
| 694 | + @Override | ||
| 695 | + public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session, | ||
| 696 | + final LoadRequest request) { | ||
| 697 | + Log.d(LOGTAG, "onLoadRequest=" + request.uri + | ||
| 698 | + " triggerUri=" + request.triggerUri + | ||
| 699 | + " where=" + request.target + | ||
| 700 | + " isRedirect=" + request.isRedirect); | ||
| 701 | + | ||
| 702 | + return GeckoResult.fromValue(AllowOrDeny.ALLOW); | ||
| 703 | + } | ||
| 704 | + | ||
| 705 | + @Override | ||
| 706 | + public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) { | ||
| 707 | + GeckoSession newSession = new GeckoSession(session.getSettings()); | ||
| 708 | + | ||
| 709 | + Intent intent = new Intent(GeckoViewActivity.this, SessionActivity.class); | ||
| 710 | + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | ||
| 711 | + intent.setAction(Intent.ACTION_VIEW); | ||
| 712 | + intent.setData(Uri.parse(uri)); | ||
| 713 | + intent.putExtra("session", newSession); | ||
| 714 | + | ||
| 715 | + startActivity(intent); | ||
| 716 | + | ||
| 717 | + return GeckoResult.fromValue(newSession); | ||
| 718 | + } | ||
| 719 | + | ||
| 720 | + private String categoryToString(final int category) { | ||
| 721 | + switch (category) { | ||
| 722 | + case WebRequestError.ERROR_CATEGORY_UNKNOWN: | ||
| 723 | + return "ERROR_CATEGORY_UNKNOWN"; | ||
| 724 | + case WebRequestError.ERROR_CATEGORY_SECURITY: | ||
| 725 | + return "ERROR_CATEGORY_SECURITY"; | ||
| 726 | + case WebRequestError.ERROR_CATEGORY_NETWORK: | ||
| 727 | + return "ERROR_CATEGORY_NETWORK"; | ||
| 728 | + case WebRequestError.ERROR_CATEGORY_CONTENT: | ||
| 729 | + return "ERROR_CATEGORY_CONTENT"; | ||
| 730 | + case WebRequestError.ERROR_CATEGORY_URI: | ||
| 731 | + return "ERROR_CATEGORY_URI"; | ||
| 732 | + case WebRequestError.ERROR_CATEGORY_PROXY: | ||
| 733 | + return "ERROR_CATEGORY_PROXY"; | ||
| 734 | + case WebRequestError.ERROR_CATEGORY_SAFEBROWSING: | ||
| 735 | + return "ERROR_CATEGORY_SAFEBROWSING"; | ||
| 736 | + default: | ||
| 737 | + return "UNKNOWN"; | ||
| 738 | + } | ||
| 739 | + } | ||
| 740 | + | ||
| 741 | + private String errorToString(final int error) { | ||
| 742 | + switch (error) { | ||
| 743 | + case WebRequestError.ERROR_UNKNOWN: | ||
| 744 | + return "ERROR_UNKNOWN"; | ||
| 745 | + case WebRequestError.ERROR_SECURITY_SSL: | ||
| 746 | + return "ERROR_SECURITY_SSL"; | ||
| 747 | + case WebRequestError.ERROR_SECURITY_BAD_CERT: | ||
| 748 | + return "ERROR_SECURITY_BAD_CERT"; | ||
| 749 | + case WebRequestError.ERROR_NET_RESET: | ||
| 750 | + return "ERROR_NET_RESET"; | ||
| 751 | + case WebRequestError.ERROR_NET_INTERRUPT: | ||
| 752 | + return "ERROR_NET_INTERRUPT"; | ||
| 753 | + case WebRequestError.ERROR_NET_TIMEOUT: | ||
| 754 | + return "ERROR_NET_TIMEOUT"; | ||
| 755 | + case WebRequestError.ERROR_CONNECTION_REFUSED: | ||
| 756 | + return "ERROR_CONNECTION_REFUSED"; | ||
| 757 | + case WebRequestError.ERROR_UNKNOWN_PROTOCOL: | ||
| 758 | + return "ERROR_UNKNOWN_PROTOCOL"; | ||
| 759 | + case WebRequestError.ERROR_UNKNOWN_HOST: | ||
| 760 | + return "ERROR_UNKNOWN_HOST"; | ||
| 761 | + case WebRequestError.ERROR_UNKNOWN_SOCKET_TYPE: | ||
| 762 | + return "ERROR_UNKNOWN_SOCKET_TYPE"; | ||
| 763 | + case WebRequestError.ERROR_UNKNOWN_PROXY_HOST: | ||
| 764 | + return "ERROR_UNKNOWN_PROXY_HOST"; | ||
| 765 | + case WebRequestError.ERROR_MALFORMED_URI: | ||
| 766 | + return "ERROR_MALFORMED_URI"; | ||
| 767 | + case WebRequestError.ERROR_REDIRECT_LOOP: | ||
| 768 | + return "ERROR_REDIRECT_LOOP"; | ||
| 769 | + case WebRequestError.ERROR_SAFEBROWSING_PHISHING_URI: | ||
| 770 | + return "ERROR_SAFEBROWSING_PHISHING_URI"; | ||
| 771 | + case WebRequestError.ERROR_SAFEBROWSING_MALWARE_URI: | ||
| 772 | + return "ERROR_SAFEBROWSING_MALWARE_URI"; | ||
| 773 | + case WebRequestError.ERROR_SAFEBROWSING_UNWANTED_URI: | ||
| 774 | + return "ERROR_SAFEBROWSING_UNWANTED_URI"; | ||
| 775 | + case WebRequestError.ERROR_SAFEBROWSING_HARMFUL_URI: | ||
| 776 | + return "ERROR_SAFEBROWSING_HARMFUL_URI"; | ||
| 777 | + case WebRequestError.ERROR_CONTENT_CRASHED: | ||
| 778 | + return "ERROR_CONTENT_CRASHED"; | ||
| 779 | + case WebRequestError.ERROR_OFFLINE: | ||
| 780 | + return "ERROR_OFFLINE"; | ||
| 781 | + case WebRequestError.ERROR_PORT_BLOCKED: | ||
| 782 | + return "ERROR_PORT_BLOCKED"; | ||
| 783 | + case WebRequestError.ERROR_PROXY_CONNECTION_REFUSED: | ||
| 784 | + return "ERROR_PROXY_CONNECTION_REFUSED"; | ||
| 785 | + case WebRequestError.ERROR_FILE_NOT_FOUND: | ||
| 786 | + return "ERROR_FILE_NOT_FOUND"; | ||
| 787 | + case WebRequestError.ERROR_FILE_ACCESS_DENIED: | ||
| 788 | + return "ERROR_FILE_ACCESS_DENIED"; | ||
| 789 | + case WebRequestError.ERROR_INVALID_CONTENT_ENCODING: | ||
| 790 | + return "ERROR_INVALID_CONTENT_ENCODING"; | ||
| 791 | + case WebRequestError.ERROR_UNSAFE_CONTENT_TYPE: | ||
| 792 | + return "ERROR_UNSAFE_CONTENT_TYPE"; | ||
| 793 | + case WebRequestError.ERROR_CORRUPTED_CONTENT: | ||
| 794 | + return "ERROR_CORRUPTED_CONTENT"; | ||
| 795 | + default: | ||
| 796 | + return "UNKNOWN"; | ||
| 797 | + } | ||
| 798 | + } | ||
| 799 | + | ||
| 800 | + private String createErrorPage(final int category, final int error) { | ||
| 801 | + if (mErrorTemplate == null) { | ||
| 802 | + InputStream stream = null; | ||
| 803 | + BufferedReader reader = null; | ||
| 804 | + StringBuilder builder = new StringBuilder(); | ||
| 805 | + try { | ||
| 806 | + stream = getResources().getAssets().open("error.html"); | ||
| 807 | + reader = new BufferedReader(new InputStreamReader(stream)); | ||
| 808 | + | ||
| 809 | + String line; | ||
| 810 | + while ((line = reader.readLine()) != null) { | ||
| 811 | + builder.append(line); | ||
| 812 | + builder.append("\n"); | ||
| 813 | + } | ||
| 814 | + | ||
| 815 | + mErrorTemplate = builder.toString(); | ||
| 816 | + } catch (IOException e) { | ||
| 817 | + Log.d(LOGTAG, "Failed to open error page template", e); | ||
| 818 | + return null; | ||
| 819 | + } finally { | ||
| 820 | + if (stream != null) { | ||
| 821 | + try { | ||
| 822 | + stream.close(); | ||
| 823 | + } catch (IOException e) { | ||
| 824 | + Log.e(LOGTAG, "Failed to close error page template stream", e); | ||
| 825 | + } | ||
| 826 | + } | ||
| 827 | + | ||
| 828 | + if (reader != null) { | ||
| 829 | + try { | ||
| 830 | + reader.close(); | ||
| 831 | + } catch (IOException e) { | ||
| 832 | + Log.e(LOGTAG, "Failed to close error page template reader", e); | ||
| 833 | + } | ||
| 834 | + } | ||
| 835 | + } | ||
| 836 | + } | ||
| 837 | + | ||
| 838 | + return GeckoViewActivity.this.createErrorPage(categoryToString(category) + " : " + errorToString(error)); | ||
| 839 | + } | ||
| 840 | + | ||
| 841 | + @Override | ||
| 842 | + public GeckoResult<String> onLoadError(final GeckoSession session, final String uri, | ||
| 843 | + final WebRequestError error) { | ||
| 844 | + Log.d(LOGTAG, "onLoadError=" + uri + | ||
| 845 | + " error category=" + error.category + | ||
| 846 | + " error=" + error.code); | ||
| 847 | + | ||
| 848 | + return GeckoResult.fromValue("data:text/html," + createErrorPage(error.category, error.code)); | ||
| 849 | + } | ||
| 850 | + } | ||
| 851 | + | ||
| 852 | + private class ExampleTrackingProtectionDelegate implements GeckoSession.TrackingProtectionDelegate { | ||
| 853 | + private int mBlockedAds = 0; | ||
| 854 | + private int mBlockedAnalytics = 0; | ||
| 855 | + private int mBlockedSocial = 0; | ||
| 856 | + private int mBlockedContent = 0; | ||
| 857 | + private int mBlockedTest = 0; | ||
| 858 | + | ||
| 859 | + private void clearCounters() { | ||
| 860 | + mBlockedAds = 0; | ||
| 861 | + mBlockedAnalytics = 0; | ||
| 862 | + mBlockedSocial = 0; | ||
| 863 | + mBlockedContent = 0; | ||
| 864 | + mBlockedTest = 0; | ||
| 865 | + } | ||
| 866 | + | ||
| 867 | + private void logCounters() { | ||
| 868 | + Log.d(LOGTAG, "Trackers blocked: " + mBlockedAds + " ads, " + | ||
| 869 | + mBlockedAnalytics + " analytics, " + | ||
| 870 | + mBlockedSocial + " social, " + | ||
| 871 | + mBlockedContent + " content, " + | ||
| 872 | + mBlockedTest + " test"); | ||
| 873 | + } | ||
| 874 | + | ||
| 875 | + @Override | ||
| 876 | + public void onTrackerBlocked(final GeckoSession session, final String uri, | ||
| 877 | + int categories) { | ||
| 878 | + Log.d(LOGTAG, "onTrackerBlocked " + categories + " (" + uri + ")"); | ||
| 879 | + if ((categories & TrackingProtectionDelegate.CATEGORY_TEST) != 0) { | ||
| 880 | + mBlockedTest++; | ||
| 881 | + } | ||
| 882 | + if ((categories & TrackingProtectionDelegate.CATEGORY_AD) != 0) { | ||
| 883 | + mBlockedAds++; | ||
| 884 | + } | ||
| 885 | + if ((categories & TrackingProtectionDelegate.CATEGORY_ANALYTIC) != 0) { | ||
| 886 | + mBlockedAnalytics++; | ||
| 887 | + } | ||
| 888 | + if ((categories & TrackingProtectionDelegate.CATEGORY_SOCIAL) != 0) { | ||
| 889 | + mBlockedSocial++; | ||
| 890 | + } | ||
| 891 | + if ((categories & TrackingProtectionDelegate.CATEGORY_CONTENT) != 0) { | ||
| 892 | + mBlockedContent++; | ||
| 893 | + } | ||
| 894 | + } | ||
| 895 | + } | ||
| 896 | +} |
| 1 | +package org.mozilla.geckoview_example; | ||
| 2 | + | ||
| 3 | +import org.mozilla.geckoview.GeckoSession; | ||
| 4 | + | ||
| 5 | +import android.content.Context; | ||
| 6 | +import android.support.v7.widget.AppCompatEditText; | ||
| 7 | +import android.view.KeyEvent; | ||
| 8 | +import android.view.View; | ||
| 9 | +import android.view.inputmethod.EditorInfo; | ||
| 10 | +import android.widget.TextView; | ||
| 11 | + | ||
| 12 | +public class LocationView extends AppCompatEditText { | ||
| 13 | + | ||
| 14 | + private CommitListener mCommitListener; | ||
| 15 | + private FocusAndCommitListener mFocusCommitListener = new FocusAndCommitListener(); | ||
| 16 | + | ||
| 17 | + public interface CommitListener { | ||
| 18 | + void onCommit(String text); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + public LocationView(Context context) { | ||
| 22 | + super(context); | ||
| 23 | + | ||
| 24 | + this.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_URI); | ||
| 25 | + this.setSingleLine(true); | ||
| 26 | + this.setSelectAllOnFocus(true); | ||
| 27 | + this.setHint(R.string.location_hint); | ||
| 28 | + | ||
| 29 | + setOnFocusChangeListener(mFocusCommitListener); | ||
| 30 | + setOnEditorActionListener(mFocusCommitListener); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public void setCommitListener(CommitListener listener) { | ||
| 34 | + mCommitListener = listener; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + private class FocusAndCommitListener implements OnFocusChangeListener, OnEditorActionListener { | ||
| 38 | + private String mInitialText; | ||
| 39 | + private boolean mCommitted; | ||
| 40 | + | ||
| 41 | + @Override | ||
| 42 | + public void onFocusChange(View view, boolean focused) { | ||
| 43 | + if (focused) { | ||
| 44 | + mInitialText = ((TextView)view).getText().toString(); | ||
| 45 | + mCommitted = false; | ||
| 46 | + } else if (!mCommitted) { | ||
| 47 | + setText(mInitialText); | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + @Override | ||
| 52 | + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { | ||
| 53 | + if (mCommitListener != null) { | ||
| 54 | + mCommitListener.onCommit(textView.getText().toString()); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + mCommitted = true; | ||
| 58 | + return true; | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | +} |
src/main/res/drawable-hdpi/ic_crash.png
0 → 100644
303 字节
src/main/res/drawable-mdpi/ic_crash.png
0 → 100644
226 字节
src/main/res/drawable-xhdpi/ic_crash.png
0 → 100644
386 字节
src/main/res/drawable-xxhdpi/ic_crash.png
0 → 100644
572 字节
src/main/res/layout/geckoview_activity.xml
0 → 100644
| 1 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | + android:id="@+id/main" | ||
| 3 | + android:layout_width="match_parent" | ||
| 4 | + android:layout_height="match_parent" | ||
| 5 | + android:orientation="vertical"> | ||
| 6 | + | ||
| 7 | + <org.mozilla.geckoview.GeckoView | ||
| 8 | + android:id="@+id/gecko_view" | ||
| 9 | + android:layout_width="match_parent" | ||
| 10 | + android:layout_height="match_parent" | ||
| 11 | + android:layout_above="@id/toolbar" | ||
| 12 | + android:scrollbars="none" | ||
| 13 | + /> | ||
| 14 | + | ||
| 15 | + <android.support.v7.widget.Toolbar | ||
| 16 | + android:id="@+id/toolbar" | ||
| 17 | + android:layout_width="match_parent" | ||
| 18 | + android:layout_height="?android:actionBarSize" | ||
| 19 | + android:layout_alignParentBottom="true"/> | ||
| 20 | + | ||
| 21 | + <ProgressBar | ||
| 22 | + android:id="@+id/page_progress" | ||
| 23 | + style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" | ||
| 24 | + android:layout_width="match_parent" | ||
| 25 | + android:layout_height="3dp" | ||
| 26 | + android:layout_alignTop="@id/gecko_view" /> | ||
| 27 | +</RelativeLayout> |
src/main/res/menu/actions.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<menu xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | + <item android:title="@string/multiprocess" android:id="@+id/action_e10s" android:checkable="true" | ||
| 4 | + android:showAsAction="never"/> | ||
| 5 | + <item android:title="@string/tracking_protection" android:id="@+id/action_tp" android:showAsAction="never" | ||
| 6 | + android:checkable="true"/> | ||
| 7 | + <item android:title="@string/private_browsing" android:checkable="true" android:id="@+id/action_pb"/> | ||
| 8 | + <item android:title="@string/forward" android:id="@+id/action_forward"/> | ||
| 9 | + <item android:title="@string/reload" android:id="@+id/action_reload"/> | ||
| 10 | +</menu> |
src/main/res/values/colors.xml
0 → 100644
src/main/res/values/ids.xml
0 → 100644
src/main/res/values/strings.xml
0 → 100644
| 1 | +<resources> | ||
| 2 | + <string name="app_name">GeckoView Example</string> | ||
| 3 | + <string name="activity_label">GeckoView Example</string> | ||
| 4 | + <string name="location_hint">Enter URL or search keywords...</string> | ||
| 5 | + <string name="username">Username</string> | ||
| 6 | + <string name="password">Password</string> | ||
| 7 | + <string name="clear_field">Clear</string> | ||
| 8 | + <string name="request_autoplay">Allow media to play on "%1$s"?</string> | ||
| 9 | + <string name="request_geolocation">Share location with "%1$s"?</string> | ||
| 10 | + <string name="request_notification">Allow notifications for "%1$s"?</string> | ||
| 11 | + <string name="request_video">Share video with "%1$s"</string> | ||
| 12 | + <string name="request_audio">Share audio with "%1$s"</string> | ||
| 13 | + <string name="request_media">Share video and audio with "%1$s"</string> | ||
| 14 | + <string name="media_back_camera">Back camera</string> | ||
| 15 | + <string name="media_front_camera">Front camera</string> | ||
| 16 | + <string name="media_microphone">Microphone</string> | ||
| 17 | + <string name="media_other">Unknown source</string> | ||
| 18 | + | ||
| 19 | + <string name="crash_native">Native</string> | ||
| 20 | + <string name="crash_java">Java</string> | ||
| 21 | + <string name="crash_content_native">Content (Native)</string> | ||
| 22 | + <string name="multiprocess">Multiprocess</string> | ||
| 23 | + <string name="tracking_protection">Tracking Protection</string> | ||
| 24 | + <string name="private_browsing">Private Browsing</string> | ||
| 25 | + <string name="forward">Forward</string> | ||
| 26 | + <string name="reload">Reload</string> | ||
| 27 | + <string name="crashed_title">GeckoView Example Crashed</string> | ||
| 28 | + <string name="crashed_text">Tap to report to Mozilla.</string> | ||
| 29 | + <string name="crashed_ignore">Ignore</string> | ||
| 30 | +</resources> |
-
请 注册 或 登录 后发表评论