胡斌

example from mozilla

.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.idea/
.DS_Store
/build
app/build
*.iml
... ...
buildDir "${topobjdir}/gradle/build/mobile/android/geckoview_example"
apply plugin: 'com.android.application'
apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
android {
compileSdkVersion project.ext.compileSdkVersion
defaultConfig {
targetSdkVersion project.ext.targetSdkVersion
minSdkVersion project.ext.minSdkVersion
manifestPlaceholders = project.ext.manifestPlaceholders
applicationId "org.mozilla.geckoview_example"
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
project.configureProductFlavors.delegate = it
project.configureProductFlavors()
}
dependencies {
implementation "com.android.support:support-annotations:$support_library_version"
implementation "com.android.support:appcompat-v7:$support_library_version"
implementation project(path: ':geckoview')
}
... ...
不能预览此文件类型
#Wed Jan 16 08:28:17 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
... ...
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
... ...
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
... ...
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/nalexander/.mozbuild/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
... ...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.geckoview_example">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner"/>
<activity
android:name=".GeckoViewActivity"
android:label="GeckoView Example"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:windowSoftInputMode="stateUnspecified|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.APP_BROWSER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="about"/>
<data android:scheme="javascript"/>
</intent-filter>
</activity>
<activity
android:name=".SessionActivity"
android:label="GeckoView Example"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:windowSoftInputMode="stateUnspecified|adjustResize">
</activity>
<service
android:name=".ExampleCrashHandler"
android:exported="false"
android:process=":crash">
</service>
</application>
</manifest>
\ No newline at end of file
... ...
<!DOCTYPE html>
<html>
<head>
<title>Boom!</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<style>
body {
background-color: red;
color: white;
font-family: sans;
}
div.container {
width: 75%;
margin: auto;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Boom!</h1>
<p>Something bad happened...</p>
<p>$ERROR</p>
</div>
</body>
</html>
\ No newline at end of file
... ...
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview_example;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.text.InputType;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource;
final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
protected static final String LOGTAG = "BasicGeckoViewPrompt";
private final Activity mActivity;
public int filePickerRequestCode = 1;
private int mFileType;
private FileCallback mFileCallback;
public BasicGeckoViewPrompt(final Activity activity) {
mActivity = activity;
}
private AlertDialog.Builder addCheckbox(final AlertDialog.Builder builder,
ViewGroup parent,
final AlertCallback callback) {
if (!callback.hasCheckbox()) {
return builder;
}
final CheckBox checkbox = new CheckBox(builder.getContext());
if (callback.getCheckboxMessage() != null) {
checkbox.setText(callback.getCheckboxMessage());
}
checkbox.setChecked(callback.getCheckboxValue());
checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton button,
final boolean checked) {
callback.setCheckboxValue(checked);
}
});
if (parent == null) {
final int padding = getViewPadding(builder);
parent = new FrameLayout(builder.getContext());
parent.setPadding(/* left */ padding, /* top */ 0,
/* right */ padding, /* bottom */ 0);
builder.setView(parent);
}
parent.addView(checkbox);
return builder;
}
public void onAlert(final GeckoSession session, final String title, final String msg,
final AlertCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(msg)
.setPositiveButton(android.R.string.ok, /* onClickListener */ null);
createStandardDialog(addCheckbox(builder, /* parent */ null, callback),
callback).show();
}
public void onButtonPrompt(final GeckoSession session, final String title,
final String msg, final String[] btnMsg,
final ButtonCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(msg);
final DialogInterface.OnClickListener listener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
callback.confirm(BUTTON_TYPE_POSITIVE);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
callback.confirm(BUTTON_TYPE_NEUTRAL);
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
callback.confirm(BUTTON_TYPE_NEGATIVE);
} else {
callback.dismiss();
}
}
};
if (btnMsg[BUTTON_TYPE_POSITIVE] != null) {
builder.setPositiveButton(btnMsg[BUTTON_TYPE_POSITIVE], listener);
}
if (btnMsg[BUTTON_TYPE_NEUTRAL] != null) {
builder.setNeutralButton(btnMsg[BUTTON_TYPE_NEUTRAL], listener);
}
if (btnMsg[BUTTON_TYPE_NEGATIVE] != null) {
builder.setNegativeButton(btnMsg[BUTTON_TYPE_NEGATIVE], listener);
}
createStandardDialog(addCheckbox(builder, /* parent */ null, callback),
callback).show();
}
private int getViewPadding(final AlertDialog.Builder builder) {
final TypedArray attr = builder.getContext().obtainStyledAttributes(
new int[] { android.R.attr.listPreferredItemPaddingLeft });
final int padding = attr.getDimensionPixelSize(0, 1);
attr.recycle();
return padding;
}
private LinearLayout addStandardLayout(final AlertDialog.Builder builder,
final String title, final String msg) {
final ScrollView scrollView = new ScrollView(builder.getContext());
final LinearLayout container = new LinearLayout(builder.getContext());
final int horizontalPadding = getViewPadding(builder);
final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0;
container.setOrientation(LinearLayout.VERTICAL);
container.setPadding(/* left */ horizontalPadding, /* top */ verticalPadding,
/* right */ horizontalPadding, /* bottom */ verticalPadding);
scrollView.addView(container);
builder.setTitle(title)
.setMessage(msg)
.setView(scrollView);
return container;
}
private AlertDialog createStandardDialog(final AlertDialog.Builder builder,
final AlertCallback callback) {
final AlertDialog dialog = builder.create();
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(final DialogInterface dialog) {
callback.dismiss();
}
});
return dialog;
}
public void onTextPrompt(final GeckoSession session, final String title,
final String msg, final String value,
final TextCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final LinearLayout container = addStandardLayout(builder, title, msg);
final EditText editText = new EditText(builder.getContext());
editText.setText(value);
container.addView(editText);
builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
callback.confirm(editText.getText().toString());
}
});
createStandardDialog(addCheckbox(builder, container, callback), callback).show();
}
public void onAuthPrompt(final GeckoSession session, final String title,
final String msg, final AuthOptions options,
final AuthCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final LinearLayout container = addStandardLayout(builder, title, msg);
final int flags = options.flags;
final int level = options.level;
final EditText username;
if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
username = new EditText(builder.getContext());
username.setHint(R.string.username);
username.setText(options.username);
container.addView(username);
} else {
username = null;
}
final EditText password = new EditText(builder.getContext());
password.setHint(R.string.password);
password.setText(options.password);
password.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
container.addView(password);
if (level != AuthOptions.AUTH_LEVEL_NONE) {
final ImageView secure = new ImageView(builder.getContext());
secure.setImageResource(android.R.drawable.ic_lock_lock);
container.addView(secure);
}
builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
callback.confirm(username.getText().toString(),
password.getText().toString());
} else {
callback.confirm(password.getText().toString());
}
}
});
createStandardDialog(addCheckbox(builder, container, callback), callback).show();
}
private static class ModifiableChoice {
public boolean modifiableSelected;
public String modifiableLabel;
public final Choice choice;
public ModifiableChoice(Choice c) {
choice = c;
modifiableSelected = choice.selected;
modifiableLabel = choice.label;
}
}
private void addChoiceItems(final int type, final ArrayAdapter<ModifiableChoice> list,
final Choice[] items, final String indent) {
if (type == Choice.CHOICE_TYPE_MENU) {
for (final Choice item : items) {
list.add(new ModifiableChoice(item));
}
return;
}
for (final Choice item : items) {
final ModifiableChoice modItem = new ModifiableChoice(item);
final Choice[] children = item.items;
if (indent != null && children == null) {
modItem.modifiableLabel = indent + modItem.modifiableLabel;
}
list.add(modItem);
if (children != null) {
final String newIndent;
if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MULTIPLE) {
newIndent = (indent != null) ? indent + '\t' : "\t";
} else {
newIndent = null;
}
addChoiceItems(type, list, children, newIndent);
}
}
}
public void onChoicePrompt(final GeckoSession session, final String title,
final String msg, final int type,
final Choice[] choices, final ChoiceCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
addStandardLayout(builder, title, msg);
final ListView list = new ListView(builder.getContext());
if (type == Choice.CHOICE_TYPE_MULTIPLE) {
list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
final ArrayAdapter<ModifiableChoice> adapter = new ArrayAdapter<ModifiableChoice>(
builder.getContext(), android.R.layout.simple_list_item_1) {
private static final int TYPE_MENU_ITEM = 0;
private static final int TYPE_MENU_CHECK = 1;
private static final int TYPE_SEPARATOR = 2;
private static final int TYPE_GROUP = 3;
private static final int TYPE_SINGLE = 4;
private static final int TYPE_MULTIPLE = 5;
private static final int TYPE_COUNT = 6;
private LayoutInflater mInflater;
private View mSeparator;
@Override
public int getViewTypeCount() {
return TYPE_COUNT;
}
@Override
public int getItemViewType(final int position) {
final ModifiableChoice item = getItem(position);
if (item.choice.separator) {
return TYPE_SEPARATOR;
} else if (type == Choice.CHOICE_TYPE_MENU) {
return item.modifiableSelected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM;
} else if (item.choice.items != null) {
return TYPE_GROUP;
} else if (type == Choice.CHOICE_TYPE_SINGLE) {
return TYPE_SINGLE;
} else if (type == Choice.CHOICE_TYPE_MULTIPLE) {
return TYPE_MULTIPLE;
} else {
throw new UnsupportedOperationException();
}
}
@Override
public boolean isEnabled(final int position) {
final ModifiableChoice item = getItem(position);
return !item.choice.separator && !item.choice.disabled &&
((type != Choice.CHOICE_TYPE_SINGLE && type != Choice.CHOICE_TYPE_MULTIPLE) ||
item.choice.items == null);
}
@Override
public View getView(final int position, View view,
final ViewGroup parent) {
final int itemType = getItemViewType(position);
final int layoutId;
if (itemType == TYPE_SEPARATOR) {
if (mSeparator == null) {
mSeparator = new View(getContext());
mSeparator.setLayoutParams(new ListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 2, itemType));
final TypedArray attr = getContext().obtainStyledAttributes(
new int[] { android.R.attr.listDivider });
mSeparator.setBackgroundResource(attr.getResourceId(0, 0));
attr.recycle();
}
return mSeparator;
} else if (itemType == TYPE_MENU_ITEM) {
layoutId = android.R.layout.simple_list_item_1;
} else if (itemType == TYPE_MENU_CHECK) {
layoutId = android.R.layout.simple_list_item_checked;
} else if (itemType == TYPE_GROUP) {
layoutId = android.R.layout.preference_category;
} else if (itemType == TYPE_SINGLE) {
layoutId = android.R.layout.simple_list_item_single_choice;
} else if (itemType == TYPE_MULTIPLE) {
layoutId = android.R.layout.simple_list_item_multiple_choice;
} else {
throw new UnsupportedOperationException();
}
if (view == null) {
if (mInflater == null) {
mInflater = LayoutInflater.from(builder.getContext());
}
view = mInflater.inflate(layoutId, parent, false);
}
final ModifiableChoice item = getItem(position);
final TextView text = (TextView) view;
text.setEnabled(!item.choice.disabled);
text.setText(item.modifiableLabel);
if (view instanceof CheckedTextView) {
final boolean selected = item.modifiableSelected;
if (itemType == TYPE_MULTIPLE) {
list.setItemChecked(position, selected);
} else {
((CheckedTextView) view).setChecked(selected);
}
}
return view;
}
};
addChoiceItems(type, adapter, choices, /* indent */ null);
list.setAdapter(adapter);
builder.setView(list);
final AlertDialog dialog;
if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MENU) {
dialog = createStandardDialog(builder, callback);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(final AdapterView<?> parent, final View v,
final int position, final long id) {
final ModifiableChoice item = adapter.getItem(position);
if (type == Choice.CHOICE_TYPE_MENU) {
final Choice[] children = item.choice.items;
if (children != null) {
// Show sub-menu.
dialog.setOnDismissListener(null);
dialog.dismiss();
onChoicePrompt(session, item.modifiableLabel, /* msg */ null,
type, children, callback);
return;
}
}
callback.confirm(item.choice);
dialog.dismiss();
}
});
} else if (type == Choice.CHOICE_TYPE_MULTIPLE) {
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(final AdapterView<?> parent, final View v,
final int position, final long id) {
final ModifiableChoice item = adapter.getItem(position);
item.modifiableSelected = ((CheckedTextView) v).isChecked();
}
});
builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog,
final int which) {
final int len = adapter.getCount();
ArrayList<String> items = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
final ModifiableChoice item = adapter.getItem(i);
if (item.modifiableSelected) {
items.add(item.choice.id);
}
}
callback.confirm(items.toArray(new String[items.size()]));
}
});
dialog = createStandardDialog(builder, callback);
} else {
throw new UnsupportedOperationException();
}
dialog.show();
}
private static int parseColor(final String value, final int def) {
try {
return Color.parseColor(value);
} catch (final IllegalArgumentException e) {
return def;
}
}
public void onColorPrompt(final GeckoSession session, final String title,
final String value, final TextCallback callback)
{
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
addStandardLayout(builder, title, /* msg */ null);
final int initial = parseColor(value, /* def */ 0);
final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(
builder.getContext(), android.R.layout.simple_list_item_1) {
private LayoutInflater mInflater;
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(final int position) {
return (getItem(position) == initial) ? 1 : 0;
}
@Override
public View getView(final int position, View view,
final ViewGroup parent) {
if (mInflater == null) {
mInflater = LayoutInflater.from(builder.getContext());
}
final int color = getItem(position);
if (view == null) {
view = mInflater.inflate((color == initial) ?
android.R.layout.simple_list_item_checked :
android.R.layout.simple_list_item_1, parent, false);
}
view.setBackgroundResource(android.R.drawable.editbox_background);
view.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
return view;
}
};
adapter.addAll(0xffff4444 /* holo_red_light */,
0xffcc0000 /* holo_red_dark */,
0xffffbb33 /* holo_orange_light */,
0xffff8800 /* holo_orange_dark */,
0xff99cc00 /* holo_green_light */,
0xff669900 /* holo_green_dark */,
0xff33b5e5 /* holo_blue_light */,
0xff0099cc /* holo_blue_dark */,
0xffaa66cc /* holo_purple */,
0xffffffff /* white */,
0xffaaaaaa /* lighter_gray */,
0xff555555 /* darker_gray */,
0xff000000 /* black */);
final ListView list = new ListView(builder.getContext());
list.setAdapter(adapter);
builder.setView(list);
final AlertDialog dialog = createStandardDialog(builder, callback);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(final AdapterView<?> parent, final View v,
final int position, final long id) {
callback.confirm(String.format("#%06x", 0xffffff & adapter.getItem(position)));
dialog.dismiss();
}
});
dialog.show();
}
private static Date parseDate(final SimpleDateFormat formatter,
final String value,
final boolean defaultToNow) {
try {
if (value != null && !value.isEmpty()) {
return formatter.parse(value);
}
} catch (final ParseException e) {
}
return defaultToNow ? new Date() : null;
}
@SuppressWarnings("deprecation")
private static void setTimePickerTime(final TimePicker picker, final Calendar cal) {
if (Build.VERSION.SDK_INT >= 23) {
picker.setHour(cal.get(Calendar.HOUR_OF_DAY));
picker.setMinute(cal.get(Calendar.MINUTE));
} else {
picker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
picker.setCurrentMinute(cal.get(Calendar.MINUTE));
}
}
@SuppressWarnings("deprecation")
private static void setCalendarTime(final Calendar cal, final TimePicker picker) {
if (Build.VERSION.SDK_INT >= 23) {
cal.set(Calendar.HOUR_OF_DAY, picker.getHour());
cal.set(Calendar.MINUTE, picker.getMinute());
} else {
cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
cal.set(Calendar.MINUTE, picker.getCurrentMinute());
}
}
public void onDateTimePrompt(final GeckoSession session, final String title,
final int type, final String value, final String min,
final String max, final TextCallback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
final String format;
if (type == DATETIME_TYPE_DATE) {
format = "yyyy-MM-dd";
} else if (type == DATETIME_TYPE_MONTH) {
format = "yyyy-MM";
} else if (type == DATETIME_TYPE_WEEK) {
format = "yyyy-'W'ww";
} else if (type == DATETIME_TYPE_TIME) {
format = "HH:mm";
} else if (type == DATETIME_TYPE_DATETIME_LOCAL) {
format = "yyyy-MM-dd'T'HH:mm";
} else {
throw new UnsupportedOperationException();
}
final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ROOT);
final Date minDate = parseDate(formatter, min, /* defaultToNow */ false);
final Date maxDate = parseDate(formatter, max, /* defaultToNow */ false);
final Date date = parseDate(formatter, value, /* defaultToNow */ true);
final Calendar cal = formatter.getCalendar();
cal.setTime(date);
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final LayoutInflater inflater = LayoutInflater.from(builder.getContext());
final DatePicker datePicker;
if (type == DATETIME_TYPE_DATE || type == DATETIME_TYPE_MONTH ||
type == DATETIME_TYPE_WEEK || type == DATETIME_TYPE_DATETIME_LOCAL) {
final int resId = builder.getContext().getResources().getIdentifier(
"date_picker_dialog", "layout", "android");
DatePicker picker = null;
if (resId != 0) {
try {
picker = (DatePicker) inflater.inflate(resId, /* root */ null);
} catch (final ClassCastException|InflateException e) {
}
}
if (picker == null) {
picker = new DatePicker(builder.getContext());
}
picker.init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH), /* listener */ null);
if (minDate != null) {
picker.setMinDate(minDate.getTime());
}
if (maxDate != null) {
picker.setMaxDate(maxDate.getTime());
}
datePicker = picker;
} else {
datePicker = null;
}
final TimePicker timePicker;
if (type == DATETIME_TYPE_TIME || type == DATETIME_TYPE_DATETIME_LOCAL) {
final int resId = builder.getContext().getResources().getIdentifier(
"time_picker_dialog", "layout", "android");
TimePicker picker = null;
if (resId != 0) {
try {
picker = (TimePicker) inflater.inflate(resId, /* root */ null);
} catch (final ClassCastException|InflateException e) {
}
}
if (picker == null) {
picker = new TimePicker(builder.getContext());
}
setTimePickerTime(picker, cal);
picker.setIs24HourView(DateFormat.is24HourFormat(builder.getContext()));
timePicker = picker;
} else {
timePicker = null;
}
final LinearLayout container = addStandardLayout(builder, title, /* msg */ null);
container.setPadding(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0);
if (datePicker != null) {
container.addView(datePicker);
}
if (timePicker != null) {
container.addView(timePicker);
}
final DialogInterface.OnClickListener listener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
if (which == DialogInterface.BUTTON_NEUTRAL) {
// Clear
callback.confirm("");
return;
}
if (datePicker != null) {
cal.set(datePicker.getYear(), datePicker.getMonth(),
datePicker.getDayOfMonth());
}
if (timePicker != null) {
setCalendarTime(cal, timePicker);
}
callback.confirm(formatter.format(cal.getTime()));
}
};
builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
.setNeutralButton(R.string.clear_field, listener)
.setPositiveButton(android.R.string.ok, listener);
createStandardDialog(builder, callback).show();
}
@TargetApi(19)
public void onFilePrompt(GeckoSession session, String title, int type,
String[] mimeTypes, FileCallback callback)
{
final Activity activity = mActivity;
if (activity == null) {
callback.dismiss();
return;
}
// Merge all given MIME types into one, using wildcard if needed.
String mimeType = null;
String mimeSubtype = null;
for (final String rawType : mimeTypes) {
final String normalizedType = rawType.trim().toLowerCase(Locale.ROOT);
final int len = normalizedType.length();
int slash = normalizedType.indexOf('/');
if (slash < 0) {
slash = len;
}
final String newType = normalizedType.substring(0, slash);
final String newSubtype = normalizedType.substring(Math.min(slash + 1, len));
if (mimeType == null) {
mimeType = newType;
} else if (!mimeType.equals(newType)) {
mimeType = "*";
}
if (mimeSubtype == null) {
mimeSubtype = newSubtype;
} else if (!mimeSubtype.equals(newSubtype)) {
mimeSubtype = "*";
}
}
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType((mimeType != null ? mimeType : "*") + '/' +
(mimeSubtype != null ? mimeSubtype : "*"));
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
if (Build.VERSION.SDK_INT >= 18 && type == FILE_TYPE_MULTIPLE) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
if (Build.VERSION.SDK_INT >= 19 && mimeTypes.length > 0) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
}
try {
mFileType = type;
mFileCallback = callback;
activity.startActivityForResult(intent, filePickerRequestCode);
} catch (final ActivityNotFoundException e) {
Log.e(LOGTAG, "Cannot launch activity", e);
callback.dismiss();
}
}
public void onFileCallbackResult(final int resultCode, final Intent data) {
if (mFileCallback == null) {
return;
}
final FileCallback callback = mFileCallback;
mFileCallback = null;
if (resultCode != Activity.RESULT_OK || data == null) {
callback.dismiss();
return;
}
final Uri uri = data.getData();
final ClipData clip = data.getClipData();
if (mFileType == FILE_TYPE_SINGLE ||
(mFileType == FILE_TYPE_MULTIPLE && clip == null)) {
callback.confirm(mActivity, uri);
} else if (mFileType == FILE_TYPE_MULTIPLE) {
if (clip == null) {
Log.w(LOGTAG, "No selected file");
callback.dismiss();
return;
}
final int count = clip.getItemCount();
final ArrayList<Uri> uris = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
uris.add(clip.getItemAt(i).getUri());
}
callback.confirm(mActivity, uris.toArray(new Uri[uris.size()]));
}
}
public void onPermissionPrompt(final GeckoSession session, final String title,
final GeckoSession.PermissionDelegate.Callback callback) {
final Activity activity = mActivity;
if (activity == null) {
callback.reject();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(title)
.setNegativeButton(android.R.string.cancel, /* onClickListener */ null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
callback.grant();
}
});
final AlertDialog dialog = builder.create();
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(final DialogInterface dialog) {
callback.reject();
}
});
dialog.show();
}
private Spinner addMediaSpinner(final Context context, final ViewGroup container,
final MediaSource[] sources, final String[] sourceNames) {
final ArrayAdapter<MediaSource> adapter = new ArrayAdapter<MediaSource>(
context, android.R.layout.simple_spinner_item) {
private View convertView(final int position, final View view) {
if (view != null) {
final MediaSource item = getItem(position);
((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name);
}
return view;
}
@Override
public View getView(final int position, View view,
final ViewGroup parent) {
return convertView(position, super.getView(position, view, parent));
}
@Override
public View getDropDownView(final int position, final View view,
final ViewGroup parent) {
return convertView(position, super.getDropDownView(position, view, parent));
}
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.addAll(sources);
final Spinner spinner = new Spinner(context);
spinner.setAdapter(adapter);
spinner.setSelection(0);
container.addView(spinner);
return spinner;
}
public void onMediaPrompt(final GeckoSession session, final String title,
final MediaSource[] video, final MediaSource[] audio,
final String[] videoNames, final String[] audioNames,
final GeckoSession.PermissionDelegate.MediaCallback callback) {
final Activity activity = mActivity;
if (activity == null || (video == null && audio == null)) {
callback.reject();
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final LinearLayout container = addStandardLayout(builder, title, /* msg */ null);
final Spinner videoSpinner;
if (video != null) {
videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames);
} else {
videoSpinner = null;
}
final Spinner audioSpinner;
if (audio != null) {
audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames);
} else {
audioSpinner = null;
}
builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
final MediaSource video = (videoSpinner != null)
? (MediaSource) videoSpinner.getSelectedItem() : null;
final MediaSource audio = (audioSpinner != null)
? (MediaSource) audioSpinner.getSelectedItem() : null;
callback.grant(video, audio);
}
});
final AlertDialog dialog = builder.create();
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(final DialogInterface dialog) {
callback.reject();
}
});
dialog.show();
}
public void onMediaPrompt(final GeckoSession session, final String title,
final MediaSource[] video, final MediaSource[] audio,
final GeckoSession.PermissionDelegate.MediaCallback callback) {
onMediaPrompt(session, title, video, audio, null, null, callback);
}
@Override
public GeckoResult<AllowOrDeny> onPopupRequest(final GeckoSession session, final String targetUri) {
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
}
... ...
package org.mozilla.geckoview_example;
import org.mozilla.geckoview.BuildConfig;
import org.mozilla.geckoview.CrashReporter;
import org.mozilla.geckoview.GeckoRuntime;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.StrictMode;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
public class ExampleCrashHandler extends Service {
private static final String LOGTAG = "ExampleCrashHandler";
private static final String CHANNEL_ID = "geckoview_example_crashes";
private static final int NOTIFY_ID = 42;
private static final String ACTION_REPORT_CRASH = "org.mozilla.geckoview_example.ACTION_REPORT_CRASH";
private static final String ACTION_DISMISS = "org.mozilla.geckoview_example.ACTION_DISMISS";
private Intent mCrashIntent;
public ExampleCrashHandler() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
stopSelf();
return Service.START_NOT_STICKY;
}
if (GeckoRuntime.ACTION_CRASHED.equals(intent.getAction())) {
mCrashIntent = intent;
Log.d(LOGTAG, "Dump File: " +
mCrashIntent.getStringExtra(GeckoRuntime.EXTRA_MINIDUMP_PATH));
Log.d(LOGTAG, "Extras File: " +
mCrashIntent.getStringExtra(GeckoRuntime.EXTRA_EXTRAS_PATH));
Log.d(LOGTAG, "Dump Success: " +
mCrashIntent.getBooleanExtra(GeckoRuntime.EXTRA_MINIDUMP_SUCCESS, false));
Log.d(LOGTAG, "Fatal: " +
mCrashIntent.getBooleanExtra(GeckoRuntime.EXTRA_CRASH_FATAL, false));
String id = createNotificationChannel();
PendingIntent reportIntent = PendingIntent.getService(
this, 0,
new Intent(ACTION_REPORT_CRASH, null,
this, ExampleCrashHandler.class), 0);
PendingIntent dismissIntent = PendingIntent.getService(
this, 0,
new Intent(ACTION_DISMISS, null,
this, ExampleCrashHandler.class), 0);
Notification notification = new NotificationCompat.Builder(this, id)
.setSmallIcon(R.drawable.ic_crash)
.setContentTitle(getResources().getString(R.string.crashed_title))
.setContentText(getResources().getString(R.string.crashed_text))
.setDefaults(Notification.DEFAULT_ALL)
.setContentIntent(reportIntent)
.addAction(0, getResources().getString(R.string.crashed_ignore), dismissIntent)
.setAutoCancel(true)
.setOngoing(false)
.build();
startForeground(NOTIFY_ID, notification);
} else if (ACTION_REPORT_CRASH.equals(intent.getAction())) {
StrictMode.ThreadPolicy oldPolicy = null;
if (BuildConfig.DEBUG) {
oldPolicy = StrictMode.getThreadPolicy();
// We do some disk I/O and network I/O on the main thread, but it's fine.
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(oldPolicy)
.permitDiskReads()
.permitDiskWrites()
.permitNetwork()
.build());
}
try {
CrashReporter.sendCrashReport(this, mCrashIntent, "GeckoViewExample");
} catch (Exception e) {
Log.e(LOGTAG, "Failed to send crash report", e);
}
if (oldPolicy != null) {
StrictMode.setThreadPolicy(oldPolicy);
}
stopSelf();
} else if (ACTION_DISMISS.equals(intent.getAction())) {
stopSelf();
}
return Service.START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private String createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "GeckoView Example Crashes", NotificationManager.IMPORTANCE_LOW);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
return CHANNEL_ID;
}
return "";
}
}
... ...
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview_example;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.BasicSelectionActionDelegate;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.WebRequestError;
import android.Manifest;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.ProgressBar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Locale;
public class GeckoViewActivity extends AppCompatActivity {
private static final String LOGTAG = "GeckoViewActivity";
private static final String DEFAULT_URL = "about:blank";
private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
private static final String FULL_ACCESSIBILITY_TREE_EXTRA = "full_accessibility_tree";
private static final String SEARCH_URI_BASE = "https://www.google.com/search?q=";
private static final String ACTION_SHUTDOWN = "org.mozilla.geckoview_example.SHUTDOWN";
private static final int REQUEST_FILE_PICKER = 1;
private static final int REQUEST_PERMISSIONS = 2;
private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 3;
private static GeckoRuntime sGeckoRuntime;
private GeckoSession mGeckoSession;
private GeckoView mGeckoView;
private boolean mUseMultiprocess;
private boolean mFullAccessibilityTree;
private boolean mUseTrackingProtection;
private boolean mUsePrivateBrowsing;
private boolean mKillProcessOnDestroy;
private LocationView mLocationView;
private String mCurrentUri;
private boolean mCanGoBack;
private boolean mCanGoForward;
private boolean mFullScreen;
private ProgressBar mProgressView;
private LinkedList<GeckoSession.WebResponseInfo> mPendingDownloads = new LinkedList<>();
private LocationView.CommitListener mCommitListener = new LocationView.CommitListener() {
@Override
public void onCommit(String text) {
if ((text.contains(".") || text.contains(":")) && !text.contains(" ")) {
mGeckoSession.loadUri(text);
} else {
mGeckoSession.loadUri(SEARCH_URI_BASE + text);
}
mGeckoView.requestFocus();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
" - application start");
setContentView(R.layout.geckoview_activity);
mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
setSupportActionBar((Toolbar)findViewById(R.id.toolbar));
mLocationView = new LocationView(this);
mLocationView.setId(R.id.url_bar);
getSupportActionBar().setCustomView(mLocationView,
new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT,
ActionBar.LayoutParams.WRAP_CONTENT));
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
mUseMultiprocess = getIntent().getBooleanExtra(USE_MULTIPROCESS_EXTRA, true);
mFullAccessibilityTree = getIntent().getBooleanExtra(FULL_ACCESSIBILITY_TREE_EXTRA, false);
mProgressView = (ProgressBar) findViewById(R.id.page_progress);
if (sGeckoRuntime == null) {
final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
new GeckoRuntimeSettings.Builder();
if (BuildConfig.DEBUG) {
// In debug builds, we want to load JavaScript resources fresh with
// each build.
runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" });
}
final Bundle extras = getIntent().getExtras();
if (extras != null) {
runtimeSettingsBuilder.extras(extras);
}
runtimeSettingsBuilder
.useContentProcessHint(mUseMultiprocess)
.remoteDebuggingEnabled(true)
.consoleOutput(true)
.trackingProtectionCategories(TrackingProtectionDelegate.CATEGORY_ALL)
.crashHandler(ExampleCrashHandler.class);
sGeckoRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build());
}
if(savedInstanceState == null) {
mGeckoSession = (GeckoSession)getIntent().getParcelableExtra("session");
if (mGeckoSession != null) {
connectSession(mGeckoSession);
if (!mGeckoSession.isOpen()) {
mGeckoSession.open(sGeckoRuntime);
}
mUseMultiprocess = mGeckoSession.getSettings().getUseMultiprocess();
mFullAccessibilityTree = mGeckoSession.getSettings().getFullAccessibilityTree();
mGeckoView.setSession(mGeckoSession);
} else {
mGeckoSession = createSession();
mGeckoView.setSession(mGeckoSession, sGeckoRuntime);
loadFromIntent(getIntent());
}
}
mLocationView.setCommitListener(mCommitListener);
}
private GeckoSession createSession() {
GeckoSession session = new GeckoSession(new GeckoSessionSettings.Builder()
.useMultiprocess(mUseMultiprocess)
.usePrivateMode(mUsePrivateBrowsing)
.useTrackingProtection(mUseTrackingProtection)
.fullAccessibilityTree(mFullAccessibilityTree)
.build());
connectSession(session);
return session;
}
private void connectSession(GeckoSession session) {
session.setContentDelegate(new ExampleContentDelegate());
session.setHistoryDelegate(new ExampleHistoryDelegate());
final ExampleTrackingProtectionDelegate tp = new ExampleTrackingProtectionDelegate();
session.setTrackingProtectionDelegate(tp);
session.setProgressDelegate(new ExampleProgressDelegate(tp));
session.setNavigationDelegate(new ExampleNavigationDelegate());
final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt(this);
prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
session.setPromptDelegate(prompt);
final ExamplePermissionDelegate permission = new ExamplePermissionDelegate();
permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
session.setPermissionDelegate(permission);
session.setSelectionActionDelegate(new BasicSelectionActionDelegate(this));
updateTrackingProtection(session);
}
private void recreateSession() {
if(mGeckoSession != null) {
mGeckoSession.close();
}
mGeckoSession = createSession();
mGeckoSession.open(sGeckoRuntime);
mGeckoView.setSession(mGeckoSession);
mGeckoSession.loadUri(mCurrentUri != null ? mCurrentUri : DEFAULT_URL);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if(savedInstanceState != null) {
mGeckoSession = mGeckoView.getSession();
} else {
recreateSession();
}
}
private void updateTrackingProtection(GeckoSession session) {
session.getSettings().setUseTrackingProtection(mUseTrackingProtection);
}
@Override
public void onBackPressed() {
if (mFullScreen) {
mGeckoSession.exitFullScreen();
return;
}
if (mCanGoBack && mGeckoSession != null) {
mGeckoSession.goBack();
return;
}
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actions, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.action_e10s).setChecked(mUseMultiprocess);
menu.findItem(R.id.action_tp).setChecked(mUseTrackingProtection);
menu.findItem(R.id.action_pb).setChecked(mUsePrivateBrowsing);
menu.findItem(R.id.action_forward).setEnabled(mCanGoForward);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_reload:
mGeckoSession.reload();
break;
case R.id.action_forward:
mGeckoSession.goForward();
break;
case R.id.action_e10s:
mUseMultiprocess = !mUseMultiprocess;
recreateSession();
break;
case R.id.action_tp:
mUseTrackingProtection = !mUseTrackingProtection;
updateTrackingProtection(mGeckoSession);
mGeckoSession.reload();
break;
case R.id.action_pb:
mUsePrivateBrowsing = !mUsePrivateBrowsing;
recreateSession();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
public void onDestroy() {
if (mKillProcessOnDestroy) {
android.os.Process.killProcess(android.os.Process.myPid());
}
super.onDestroy();
}
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
if (ACTION_SHUTDOWN.equals(intent.getAction())) {
mKillProcessOnDestroy = true;
if (sGeckoRuntime != null) {
sGeckoRuntime.shutdown();
}
finish();
return;
}
setIntent(intent);
if (intent.getData() != null) {
loadFromIntent(intent);
}
}
private void loadFromIntent(final Intent intent) {
final Uri uri = intent.getData();
mGeckoSession.loadUri(uri != null ? uri.toString() : DEFAULT_URL);
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode,
final Intent data) {
if (requestCode == REQUEST_FILE_PICKER) {
final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
mGeckoSession.getPromptDelegate();
prompt.onFileCallbackResult(resultCode, data);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(final int requestCode,
final String[] permissions,
final int[] grantResults) {
if (requestCode == REQUEST_PERMISSIONS) {
final ExamplePermissionDelegate permission = (ExamplePermissionDelegate)
mGeckoSession.getPermissionDelegate();
permission.onRequestPermissionsResult(permissions, grantResults);
} else if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
continueDownloads();
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void continueDownloads() {
LinkedList<GeckoSession.WebResponseInfo> downloads = mPendingDownloads;
mPendingDownloads = new LinkedList<>();
for (GeckoSession.WebResponseInfo response : downloads) {
downloadFile(response);
}
}
private void downloadFile(GeckoSession.WebResponseInfo response) {
mGeckoSession
.getUserAgent()
.then(new GeckoResult.OnValueListener<String, Void>() {
@Override
public GeckoResult<Void> onValue(String userAgent) throws Throwable {
downloadFile(response, userAgent);
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) throws Throwable {
// getUserAgent() cannot fail.
throw new IllegalStateException("Could not get UserAgent string.");
}
});
}
private void downloadFile(GeckoSession.WebResponseInfo response, String userAgent) {
if (ContextCompat.checkSelfPermission(GeckoViewActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
mPendingDownloads.add(response);
ActivityCompat.requestPermissions(GeckoViewActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_EXTERNAL_STORAGE);
return;
}
final Uri uri = Uri.parse(response.uri);
final String filename = response.filename != null ? response.filename : uri.getLastPathSegment();
DownloadManager manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request req = new DownloadManager.Request(uri);
req.setMimeType(response.contentType);
req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
req.addRequestHeader("User-Agent", userAgent);
manager.enqueue(req);
}
private String mErrorTemplate;
private String createErrorPage(final String error) {
if (mErrorTemplate == null) {
InputStream stream = null;
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
stream = getResources().getAssets().open("error.html");
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
mErrorTemplate = builder.toString();
} catch (IOException e) {
Log.d(LOGTAG, "Failed to open error page template", e);
return null;
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template stream", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template reader", e);
}
}
}
}
return mErrorTemplate.replace("$ERROR", error);
}
private class ExampleHistoryDelegate implements GeckoSession.HistoryDelegate {
private final HashSet<String> mVisitedURLs;
private ExampleHistoryDelegate() {
mVisitedURLs = new HashSet<String>();
}
@Override
public GeckoResult<Boolean> onVisited(GeckoSession session, String url,
String lastVisitedURL, int flags) {
Log.i(LOGTAG, "Visited URL: " + url);
mVisitedURLs.add(url);
return GeckoResult.fromValue(true);
}
@Override
public GeckoResult<boolean[]> getVisited(GeckoSession session, String[] urls) {
boolean[] visited = new boolean[urls.length];
for (int i = 0; i < urls.length; i++) {
visited[i] = mVisitedURLs.contains(urls[i]);
}
return GeckoResult.fromValue(visited);
}
}
private class ExampleContentDelegate implements GeckoSession.ContentDelegate {
@Override
public void onTitleChange(GeckoSession session, String title) {
Log.i(LOGTAG, "Content title changed to " + title);
}
@Override
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
getWindow().setFlags(fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
mFullScreen = fullScreen;
if (fullScreen) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
}
@Override
public void onFocusRequest(final GeckoSession session) {
Log.i(LOGTAG, "Content requesting focus");
}
@Override
public void onCloseRequest(final GeckoSession session) {
if (session == mGeckoSession) {
finish();
}
}
@Override
public void onContextMenu(final GeckoSession session,
int screenX, int screenY,
final ContextElement element) {
Log.d(LOGTAG, "onContextMenu screenX=" + screenX +
" screenY=" + screenY +
" type=" + element.type +
" linkUri=" + element.linkUri +
" title=" + element.title +
" alt=" + element.altText +
" srcUri=" + element.srcUri);
}
@Override
public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo response) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndTypeAndNormalize(Uri.parse(response.uri), response.contentType);
startActivity(intent);
} catch (ActivityNotFoundException e) {
downloadFile(response);
}
}
@Override
public void onCrash(GeckoSession session) {
Log.e(LOGTAG, "Crashed, reopening session");
session.open(sGeckoRuntime);
session.loadUri(DEFAULT_URL);
}
@Override
public void onFirstComposite(final GeckoSession session) {
Log.d(LOGTAG, "onFirstComposite");
}
}
private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate {
private ExampleTrackingProtectionDelegate mTp;
private ExampleProgressDelegate(final ExampleTrackingProtectionDelegate tp) {
mTp = tp;
}
@Override
public void onPageStart(GeckoSession session, String url) {
Log.i(LOGTAG, "Starting to load page at " + url);
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
" - page load start");
mTp.clearCounters();
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
Log.i(LOGTAG, "Stopping page load " + (success ? "successfully" : "unsuccessfully"));
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
" - page load stop");
mTp.logCounters();
}
@Override
public void onProgressChange(GeckoSession session, int progress) {
Log.i(LOGTAG, "onProgressChange " + progress);
mProgressView.setProgress(progress);
if (progress > 0 && progress < 100) {
mProgressView.setVisibility(View.VISIBLE);
} else {
mProgressView.setVisibility(View.GONE);
}
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
Log.i(LOGTAG, "Security status changed to " + securityInfo.securityMode);
}
}
private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate {
public int androidPermissionRequestCode = 1;
private Callback mCallback;
public void onRequestPermissionsResult(final String[] permissions,
final int[] grantResults) {
if (mCallback == null) {
return;
}
final Callback cb = mCallback;
mCallback = null;
for (final int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
// At least one permission was not granted.
cb.reject();
return;
}
}
cb.grant();
}
@Override
public void onAndroidPermissionsRequest(final GeckoSession session, final String[] permissions,
final Callback callback) {
if (Build.VERSION.SDK_INT >= 23) {
// requestPermissions was introduced in API 23.
mCallback = callback;
requestPermissions(permissions, androidPermissionRequestCode);
} else {
callback.grant();
}
}
@Override
public void onContentPermissionRequest(final GeckoSession session, final String uri,
final int type, final Callback callback) {
final int resId;
if (PERMISSION_GEOLOCATION == type) {
resId = R.string.request_geolocation;
} else if (PERMISSION_DESKTOP_NOTIFICATION == type) {
resId = R.string.request_notification;
} else if (PERMISSION_AUTOPLAY_MEDIA == type) {
resId = R.string.request_autoplay;
} else {
Log.w(LOGTAG, "Unknown permission: " + type);
callback.reject();
return;
}
final String title = getString(resId, Uri.parse(uri).getAuthority());
final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
mGeckoSession.getPromptDelegate();
prompt.onPermissionPrompt(session, title, callback);
}
private String[] normalizeMediaName(final MediaSource[] sources) {
if (sources == null) {
return null;
}
String[] res = new String[sources.length];
for (int i = 0; i < sources.length; i++) {
final int mediaSource = sources[i].source;
final String name = sources[i].name;
if (MediaSource.SOURCE_CAMERA == mediaSource) {
if (name.toLowerCase(Locale.ENGLISH).contains("front")) {
res[i] = getString(R.string.media_front_camera);
} else {
res[i] = getString(R.string.media_back_camera);
}
} else if (!name.isEmpty()) {
res[i] = name;
} else if (MediaSource.SOURCE_MICROPHONE == mediaSource) {
res[i] = getString(R.string.media_microphone);
} else {
res[i] = getString(R.string.media_other);
}
}
return res;
}
@Override
public void onMediaPermissionRequest(final GeckoSession session, final String uri,
final MediaSource[] video, final MediaSource[] audio,
final MediaCallback callback) {
final String host = Uri.parse(uri).getAuthority();
final String title;
if (audio == null) {
title = getString(R.string.request_video, host);
} else if (video == null) {
title = getString(R.string.request_audio, host);
} else {
title = getString(R.string.request_media, host);
}
String[] videoNames = normalizeMediaName(video);
String[] audioNames = normalizeMediaName(audio);
final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
mGeckoSession.getPromptDelegate();
prompt.onMediaPrompt(session, title, video, audio, videoNames, audioNames, callback);
}
}
private class ExampleNavigationDelegate implements GeckoSession.NavigationDelegate {
@Override
public void onLocationChange(GeckoSession session, final String url) {
mLocationView.setText(url);
mCurrentUri = url;
}
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
mCanGoBack = canGoBack;
}
@Override
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
mCanGoForward = canGoForward;
}
@Override
public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session,
final LoadRequest request) {
Log.d(LOGTAG, "onLoadRequest=" + request.uri +
" triggerUri=" + request.triggerUri +
" where=" + request.target +
" isRedirect=" + request.isRedirect);
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
@Override
public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
GeckoSession newSession = new GeckoSession(session.getSettings());
Intent intent = new Intent(GeckoViewActivity.this, SessionActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(uri));
intent.putExtra("session", newSession);
startActivity(intent);
return GeckoResult.fromValue(newSession);
}
private String categoryToString(final int category) {
switch (category) {
case WebRequestError.ERROR_CATEGORY_UNKNOWN:
return "ERROR_CATEGORY_UNKNOWN";
case WebRequestError.ERROR_CATEGORY_SECURITY:
return "ERROR_CATEGORY_SECURITY";
case WebRequestError.ERROR_CATEGORY_NETWORK:
return "ERROR_CATEGORY_NETWORK";
case WebRequestError.ERROR_CATEGORY_CONTENT:
return "ERROR_CATEGORY_CONTENT";
case WebRequestError.ERROR_CATEGORY_URI:
return "ERROR_CATEGORY_URI";
case WebRequestError.ERROR_CATEGORY_PROXY:
return "ERROR_CATEGORY_PROXY";
case WebRequestError.ERROR_CATEGORY_SAFEBROWSING:
return "ERROR_CATEGORY_SAFEBROWSING";
default:
return "UNKNOWN";
}
}
private String errorToString(final int error) {
switch (error) {
case WebRequestError.ERROR_UNKNOWN:
return "ERROR_UNKNOWN";
case WebRequestError.ERROR_SECURITY_SSL:
return "ERROR_SECURITY_SSL";
case WebRequestError.ERROR_SECURITY_BAD_CERT:
return "ERROR_SECURITY_BAD_CERT";
case WebRequestError.ERROR_NET_RESET:
return "ERROR_NET_RESET";
case WebRequestError.ERROR_NET_INTERRUPT:
return "ERROR_NET_INTERRUPT";
case WebRequestError.ERROR_NET_TIMEOUT:
return "ERROR_NET_TIMEOUT";
case WebRequestError.ERROR_CONNECTION_REFUSED:
return "ERROR_CONNECTION_REFUSED";
case WebRequestError.ERROR_UNKNOWN_PROTOCOL:
return "ERROR_UNKNOWN_PROTOCOL";
case WebRequestError.ERROR_UNKNOWN_HOST:
return "ERROR_UNKNOWN_HOST";
case WebRequestError.ERROR_UNKNOWN_SOCKET_TYPE:
return "ERROR_UNKNOWN_SOCKET_TYPE";
case WebRequestError.ERROR_UNKNOWN_PROXY_HOST:
return "ERROR_UNKNOWN_PROXY_HOST";
case WebRequestError.ERROR_MALFORMED_URI:
return "ERROR_MALFORMED_URI";
case WebRequestError.ERROR_REDIRECT_LOOP:
return "ERROR_REDIRECT_LOOP";
case WebRequestError.ERROR_SAFEBROWSING_PHISHING_URI:
return "ERROR_SAFEBROWSING_PHISHING_URI";
case WebRequestError.ERROR_SAFEBROWSING_MALWARE_URI:
return "ERROR_SAFEBROWSING_MALWARE_URI";
case WebRequestError.ERROR_SAFEBROWSING_UNWANTED_URI:
return "ERROR_SAFEBROWSING_UNWANTED_URI";
case WebRequestError.ERROR_SAFEBROWSING_HARMFUL_URI:
return "ERROR_SAFEBROWSING_HARMFUL_URI";
case WebRequestError.ERROR_CONTENT_CRASHED:
return "ERROR_CONTENT_CRASHED";
case WebRequestError.ERROR_OFFLINE:
return "ERROR_OFFLINE";
case WebRequestError.ERROR_PORT_BLOCKED:
return "ERROR_PORT_BLOCKED";
case WebRequestError.ERROR_PROXY_CONNECTION_REFUSED:
return "ERROR_PROXY_CONNECTION_REFUSED";
case WebRequestError.ERROR_FILE_NOT_FOUND:
return "ERROR_FILE_NOT_FOUND";
case WebRequestError.ERROR_FILE_ACCESS_DENIED:
return "ERROR_FILE_ACCESS_DENIED";
case WebRequestError.ERROR_INVALID_CONTENT_ENCODING:
return "ERROR_INVALID_CONTENT_ENCODING";
case WebRequestError.ERROR_UNSAFE_CONTENT_TYPE:
return "ERROR_UNSAFE_CONTENT_TYPE";
case WebRequestError.ERROR_CORRUPTED_CONTENT:
return "ERROR_CORRUPTED_CONTENT";
default:
return "UNKNOWN";
}
}
private String createErrorPage(final int category, final int error) {
if (mErrorTemplate == null) {
InputStream stream = null;
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
stream = getResources().getAssets().open("error.html");
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
mErrorTemplate = builder.toString();
} catch (IOException e) {
Log.d(LOGTAG, "Failed to open error page template", e);
return null;
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template stream", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template reader", e);
}
}
}
}
return GeckoViewActivity.this.createErrorPage(categoryToString(category) + " : " + errorToString(error));
}
@Override
public GeckoResult<String> onLoadError(final GeckoSession session, final String uri,
final WebRequestError error) {
Log.d(LOGTAG, "onLoadError=" + uri +
" error category=" + error.category +
" error=" + error.code);
return GeckoResult.fromValue("data:text/html," + createErrorPage(error.category, error.code));
}
}
private class ExampleTrackingProtectionDelegate implements GeckoSession.TrackingProtectionDelegate {
private int mBlockedAds = 0;
private int mBlockedAnalytics = 0;
private int mBlockedSocial = 0;
private int mBlockedContent = 0;
private int mBlockedTest = 0;
private void clearCounters() {
mBlockedAds = 0;
mBlockedAnalytics = 0;
mBlockedSocial = 0;
mBlockedContent = 0;
mBlockedTest = 0;
}
private void logCounters() {
Log.d(LOGTAG, "Trackers blocked: " + mBlockedAds + " ads, " +
mBlockedAnalytics + " analytics, " +
mBlockedSocial + " social, " +
mBlockedContent + " content, " +
mBlockedTest + " test");
}
@Override
public void onTrackerBlocked(final GeckoSession session, final String uri,
int categories) {
Log.d(LOGTAG, "onTrackerBlocked " + categories + " (" + uri + ")");
if ((categories & TrackingProtectionDelegate.CATEGORY_TEST) != 0) {
mBlockedTest++;
}
if ((categories & TrackingProtectionDelegate.CATEGORY_AD) != 0) {
mBlockedAds++;
}
if ((categories & TrackingProtectionDelegate.CATEGORY_ANALYTIC) != 0) {
mBlockedAnalytics++;
}
if ((categories & TrackingProtectionDelegate.CATEGORY_SOCIAL) != 0) {
mBlockedSocial++;
}
if ((categories & TrackingProtectionDelegate.CATEGORY_CONTENT) != 0) {
mBlockedContent++;
}
}
}
}
... ...
package org.mozilla.geckoview_example;
import org.mozilla.geckoview.GeckoSession;
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
public class LocationView extends AppCompatEditText {
private CommitListener mCommitListener;
private FocusAndCommitListener mFocusCommitListener = new FocusAndCommitListener();
public interface CommitListener {
void onCommit(String text);
}
public LocationView(Context context) {
super(context);
this.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_URI);
this.setSingleLine(true);
this.setSelectAllOnFocus(true);
this.setHint(R.string.location_hint);
setOnFocusChangeListener(mFocusCommitListener);
setOnEditorActionListener(mFocusCommitListener);
}
public void setCommitListener(CommitListener listener) {
mCommitListener = listener;
}
private class FocusAndCommitListener implements OnFocusChangeListener, OnEditorActionListener {
private String mInitialText;
private boolean mCommitted;
@Override
public void onFocusChange(View view, boolean focused) {
if (focused) {
mInitialText = ((TextView)view).getText().toString();
mCommitted = false;
} else if (!mCommitted) {
setText(mInitialText);
}
}
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if (mCommitListener != null) {
mCommitListener.onCommit(textView.getText().toString());
}
mCommitted = true;
return true;
}
}
}
... ...
package org.mozilla.geckoview_example;
public class SessionActivity extends GeckoViewActivity {
}
... ...
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.mozilla.geckoview.GeckoView
android:id="@+id/gecko_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/toolbar"
android:scrollbars="none"
/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize"
android:layout_alignParentBottom="true"/>
<ProgressBar
android:id="@+id/page_progress"
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_alignTop="@id/gecko_view" />
</RelativeLayout>
... ...
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/multiprocess" android:id="@+id/action_e10s" android:checkable="true"
android:showAsAction="never"/>
<item android:title="@string/tracking_protection" android:id="@+id/action_tp" android:showAsAction="never"
android:checkable="true"/>
<item android:title="@string/private_browsing" android:checkable="true" android:id="@+id/action_pb"/>
<item android:title="@string/forward" android:id="@+id/action_forward"/>
<item android:title="@string/reload" android:id="@+id/action_reload"/>
</menu>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
... ...
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="url_bar" type="id"/>
</resources>
... ...
<resources>
<string name="app_name">GeckoView Example</string>
<string name="activity_label">GeckoView Example</string>
<string name="location_hint">Enter URL or search keywords...</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="clear_field">Clear</string>
<string name="request_autoplay">Allow media to play on "%1$s"?</string>
<string name="request_geolocation">Share location with "%1$s"?</string>
<string name="request_notification">Allow notifications for "%1$s"?</string>
<string name="request_video">Share video with "%1$s"</string>
<string name="request_audio">Share audio with "%1$s"</string>
<string name="request_media">Share video and audio with "%1$s"</string>
<string name="media_back_camera">Back camera</string>
<string name="media_front_camera">Front camera</string>
<string name="media_microphone">Microphone</string>
<string name="media_other">Unknown source</string>
<string name="crash_native">Native</string>
<string name="crash_java">Java</string>
<string name="crash_content_native">Content (Native)</string>
<string name="multiprocess">Multiprocess</string>
<string name="tracking_protection">Tracking Protection</string>
<string name="private_browsing">Private Browsing</string>
<string name="forward">Forward</string>
<string name="reload">Reload</string>
<string name="crashed_title">GeckoView Example Crashed</string>
<string name="crashed_text">Tap to report to Mozilla.</string>
<string name="crashed_ignore">Ignore</string>
</resources>
... ...