胡斌

example from mozilla

  1 +.gradle
  2 +/local.properties
  3 +/.idea/workspace.xml
  4 +/.idea/libraries
  5 +.idea/
  6 +.DS_Store
  7 +/build
  8 +app/build
  9 +*.iml
  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 +}
不能预览此文件类型
  1 +#Wed Jan 16 08:28:17 CST 2019
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionPath=wrapper/dists
  4 +zipStoreBase=GRADLE_USER_HOME
  5 +zipStorePath=wrapper/dists
  6 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
  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" "$@"
  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
  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 +#}
  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>
  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 +}
  1 +package org.mozilla.geckoview_example;
  2 +
  3 +public class SessionActivity extends GeckoViewActivity {
  4 +}
  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>
  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>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <color name="colorPrimary">#3F51B5</color>
  4 + <color name="colorPrimaryDark">#303F9F</color>
  5 + <color name="colorAccent">#FF4081</color>
  6 +</resources>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <item name="url_bar" type="id"/>
  4 +</resources>
  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>