This commit is contained in:
nicco 2018-09-05 15:49:13 +02:00
parent d9ebfa4105
commit de75042c52
194 changed files with 25169 additions and 0 deletions

View File

@ -0,0 +1,14 @@
# Non-project-specific build files:
build.xml
local.properties
/gradlew
/gradlew.bat
/gradle
# Ant builds
ant-build
ant-gen
# Eclipse builds
gen
out
# Gradle builds
**/build

View File

@ -0,0 +1,2 @@
#Tue Sep 04 20:29:04 CEST 2018
gradle.version=4.1

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
<uses-sdk android:minSdkVersion="16" />
</manifest>

View File

@ -0,0 +1,137 @@
/* Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
ext {
apply from: 'cordova.gradle'
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
buildscript {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
}
}
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
group = 'org.apache.cordova'
version = '7.0.0'
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
}
}
install {
repositories.mavenInstaller {
pom {
project {
packaging 'aar'
name 'Cordova'
url 'https://cordova.apache.org'
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'stevengill'
name 'Steve Gill'
}
}
scm {
connection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
developerConnection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
url 'https://git-wip-us.apache.org/repos/asf?p=cordova-android'
}
}
}
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
artifacts {
archives sourcesJar
}
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_KEY')
configurations = ['archives']
pkg {
repo = 'maven'
name = 'cordova-android'
userOrg = 'cordova'
licenses = ['Apache-2.0']
vcsUrl = 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
websiteUrl = 'https://cordova.apache.org'
issueTrackerUrl = 'https://issues.apache.org/jira/browse/CB'
publicDownloadNumbers = true
licenses = ['Apache-2.0']
labels = ['android', 'cordova', 'phonegap']
version {
name = '7.0.0'
released = new Date()
vcsTag = '7.0.0'
}
}
}

View File

@ -0,0 +1,205 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.util.regex.Pattern
import groovy.swing.SwingBuilder
String doEnsureValueExists(filePath, props, key) {
if (props.get(key) == null) {
throw new GradleException(filePath + ': Missing key required "' + key + '"')
}
return props.get(key)
}
String doGetProjectTarget() {
def props = new Properties()
def propertiesFile = 'project.properties';
if(!(file(propertiesFile).exists())) {
propertiesFile = '../project.properties';
}
file(propertiesFile).withReader { reader ->
props.load(reader)
}
return doEnsureValueExists('project.properties', props, 'target')
}
String[] getAvailableBuildTools() {
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
buildToolsDir.list()
.findAll { it ==~ /[0-9.]+/ }
.sort { a, b -> compareVersions(b, a) }
}
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
def availableBuildToolsVersions
try {
availableBuildToolsVersions = getAvailableBuildTools()
} catch (e) {
println "An exception occurred while trying to find the Android build tools."
throw e
}
if (availableBuildToolsVersions.length > 0) {
def highestBuildToolsVersion = availableBuildToolsVersions[0]
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
throw new RuntimeException(
"No usable Android build tools found. Highest installed version is " +
highestBuildToolsVersion + "; minimum version required is " +
minBuildToolsVersion + ".")
}
highestBuildToolsVersion
} else {
throw new RuntimeException(
"No installed build tools found. Install the Android build tools version " +
minBuildToolsVersion + " or higher.")
}
}
// Return the first non-zero result of subtracting version list elements
// pairwise. If they are all identical, return the difference in length of
// the two lists.
int compareVersionList(Collection aParts, Collection bParts) {
def pairs = ([aParts, bParts]).transpose()
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
}
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
// elements are identical, the longer version is the largest by this method.
// Examples:
// "19.0.0" > "19"
// "19.0.1" > "19.0.0"
// "19.1.0" > "19.0.1"
// "19" > "18.999.999"
int compareVersions(String a, String b) {
def aParts = a.tokenize('.').collect {it.toInteger()}
def bParts = b.tokenize('.').collect {it.toInteger()}
compareVersionList(aParts, bParts)
}
String getAndroidSdkDir() {
def rootDir = project.rootDir
def androidSdkDir = null
String envVar = System.getenv("ANDROID_HOME")
def localProperties = new File(rootDir, 'local.properties')
String systemProperty = System.getProperty("android.home")
if (envVar != null) {
androidSdkDir = envVar
} else if (localProperties.exists()) {
Properties properties = new Properties()
localProperties.withInputStream { instr ->
properties.load(instr)
}
def sdkDirProp = properties.getProperty('sdk.dir')
if (sdkDirProp != null) {
androidSdkDir = sdkDirProp
} else {
sdkDirProp = properties.getProperty('android.dir')
if (sdkDirProp != null) {
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
}
}
}
if (androidSdkDir == null && systemProperty != null) {
androidSdkDir = systemProperty
}
if (androidSdkDir == null) {
throw new RuntimeException(
"Unable to determine Android SDK directory.")
}
androidSdkDir
}
def doExtractIntFromManifest(name) {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return new BigInteger(matcher.group(1))
}
def doExtractStringFromManifest(name) {
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
def pattern = Pattern.compile(name + "=\"(\\S+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
return matcher.group(1)
}
def doPromptForPassword(msg) {
if (System.console() == null) {
def ret = null
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox {
label(text: msg)
def input = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
ret = input.password;
dispose();
})
}
}
}
if (!ret) {
throw new GradleException('User canceled build')
}
return new String(ret)
} else {
return System.console().readPassword('\n' + msg);
}
}
def doGetConfigXml() {
def xml = file("res/xml/config.xml").getText()
// Disable namespace awareness since Cordova doesn't use them properly
return new XmlParser(false, false).parseText(xml)
}
def doGetConfigPreference(name, defaultValue) {
name = name.toLowerCase()
def root = doGetConfigXml()
def ret = defaultValue
root.preference.each { it ->
def attrName = it.attribute("name")
if (attrName && attrName.toLowerCase() == name) {
ret = it.attribute("value")
}
}
return ret
}
// Properties exported here are visible to all plugins.
ext {
// These helpers are shared, but are not guaranteed to be stable / unchanged.
privateHelpers = {}
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
// These helpers can be used by plugins / projects and will not change.
cdvHelpers = {}
// Returns a XmlParser for the config.xml. Added in 4.1.0.
cdvHelpers.getConfigXml = { doGetConfigXml() }
// Returns the value for the desired <preference>. Added in 4.1.0.
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
}

View File

@ -0,0 +1,16 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-26
apk-configurations=
renderscript.opt.level=O0
android.library=true

View File

@ -0,0 +1,69 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
/**
* The Class AuthenticationToken defines the userName and password to be used for authenticating a web resource
*/
public class AuthenticationToken {
private String userName;
private String password;
/**
* Gets the user name.
*
* @return the user name
*/
public String getUserName() {
return userName;
}
/**
* Sets the user name.
*
* @param userName
* the new user name
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
* Gets the password.
*
* @return the password
*/
public String getPassword() {
return password;
}
/**
* Sets the password.
*
* @param password
* the new password
*/
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,70 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
/*
* This is a utility class that allows us to get the BuildConfig variable, which is required
* for the use of different providers. This is not guaranteed to work, and it's better for this
* to be set in the build step in config.xml
*
*/
import android.app.Activity;
import android.content.Context;
import java.lang.reflect.Field;
public class BuildHelper {
private static String TAG="BuildHelper";
/*
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
* that read the Build Configuration.
*
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
* StackOverflow. This is annoying as hell! However, this method does not work with
* ProGuard, and you should use the config.xml to define the application_id
*
*/
public static Object getBuildConfigValue(Context ctx, String key)
{
try
{
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
Field field = clazz.getField(key);
return field.get(null);
} catch (ClassNotFoundException e) {
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
e.printStackTrace();
} catch (NoSuchFieldException e) {
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
} catch (IllegalAccessException e) {
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,142 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONArray;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONObject;
public class CallbackContext {
private static final String LOG_TAG = "CordovaPlugin";
private String callbackId;
private CordovaWebView webView;
protected boolean finished;
private int changingThreads;
public CallbackContext(String callbackId, CordovaWebView webView) {
this.callbackId = callbackId;
this.webView = webView;
}
public boolean isFinished() {
return finished;
}
public boolean isChangingThreads() {
return changingThreads > 0;
}
public String getCallbackId() {
return callbackId;
}
public void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (finished) {
LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = !pluginResult.getKeepCallback();
}
}
webView.sendPluginResult(pluginResult, callbackId);
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(JSONObject message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(String message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(JSONArray message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(byte[] message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*
* @param message The message to add to the success result.
*/
public void success(int message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
/**
* Helper for success callbacks that just returns the Status.OK by default
*/
public void success() {
sendPluginResult(new PluginResult(PluginResult.Status.OK));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
*/
public void error(JSONObject message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
*/
public void error(String message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
/**
* Helper for error callbacks that just returns the Status.ERROR by default
*
* @param message The message to add to the error result.
*/
public void error(int message) {
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
}
}

View File

@ -0,0 +1,65 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.util.Pair;
import android.util.SparseArray;
/**
* Provides a collection that maps unique request codes to CordovaPlugins and Integers.
* Used to ensure that when plugins make requests for runtime permissions, those requests do not
* collide with requests from other plugins that use the same request code value.
*/
public class CallbackMap {
private int currentCallbackId = 0;
private SparseArray<Pair<CordovaPlugin, Integer>> callbacks;
public CallbackMap() {
this.callbacks = new SparseArray<Pair<CordovaPlugin, Integer>>();
}
/**
* Stores a CordovaPlugin and request code and returns a new unique request code to use
* in a permission request.
*
* @param receiver The plugin that is making the request
* @param requestCode The original request code used by the plugin
* @return A unique request code that can be used to retrieve this callback
* with getAndRemoveCallback()
*/
public synchronized int registerCallback(CordovaPlugin receiver, int requestCode) {
int mappedId = this.currentCallbackId++;
callbacks.put(mappedId, new Pair<CordovaPlugin, Integer>(receiver, requestCode));
return mappedId;
}
/**
* Retrieves and removes a callback stored in the map using the mapped request code
* obtained from registerCallback()
*
* @param mappedId The request code obtained from registerCallback()
* @return The CordovaPlugin and orignal request code that correspond to the
* given mappedCode
*/
public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
Pair<CordovaPlugin, Integer> callback = callbacks.get(mappedId);
callbacks.remove(mappedId);
return callback;
}
}

View File

@ -0,0 +1,71 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.List;
import android.app.Activity;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
public class Config {
private static final String TAG = "Config";
static ConfigXmlParser parser;
private Config() {
}
public static void init(Activity action) {
parser = new ConfigXmlParser();
parser.parse(action);
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
}
// Intended to be used for testing only; creates an empty configuration.
public static void init() {
if (parser == null) {
parser = new ConfigXmlParser();
}
}
public static String getStartUrl() {
if (parser == null) {
return "file:///android_asset/www/index.html";
}
return parser.getLaunchUrl();
}
public static String getErrorUrl() {
return parser.getPreferences().getString("errorurl", null);
}
public static List<PluginEntry> getPluginEntries() {
return parser.getPluginEntries();
}
public static CordovaPreferences getPreferences() {
return parser.getPreferences();
}
public static boolean isInitialized() {
return parser != null;
}
}

View File

@ -0,0 +1,145 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
public class ConfigXmlParser {
private static String TAG = "ConfigXmlParser";
private String launchUrl = "file:///android_asset/www/index.html";
private CordovaPreferences prefs = new CordovaPreferences();
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
public CordovaPreferences getPreferences() {
return prefs;
}
public ArrayList<PluginEntry> getPluginEntries() {
return pluginEntries;
}
public String getLaunchUrl() {
return launchUrl;
}
public void parse(Context action) {
// First checking the class namespace for config.xml
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
if (id == 0) {
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
if (id == 0) {
LOG.e(TAG, "res/xml/config.xml is missing!");
return;
}
}
parse(action.getResources().getXml(id));
}
boolean insideFeature = false;
String service = "", pluginClass = "", paramType = "";
boolean onload = false;
public void parse(XmlPullParser xml) {
int eventType = -1;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
handleStartTag(xml);
}
else if (eventType == XmlPullParser.END_TAG)
{
handleEndTag(xml);
}
try {
eventType = xml.next();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void handleStartTag(XmlPullParser xml) {
String strNode = xml.getName();
if (strNode.equals("feature")) {
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
//Set the bit for reading params
insideFeature = true;
service = xml.getAttributeValue(null, "name");
}
else if (insideFeature && strNode.equals("param")) {
paramType = xml.getAttributeValue(null, "name");
if (paramType.equals("service")) // check if it is using the older service param
service = xml.getAttributeValue(null, "value");
else if (paramType.equals("package") || paramType.equals("android-package"))
pluginClass = xml.getAttributeValue(null,"value");
else if (paramType.equals("onload"))
onload = "true".equals(xml.getAttributeValue(null, "value"));
}
else if (strNode.equals("preference")) {
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
String value = xml.getAttributeValue(null, "value");
prefs.set(name, value);
}
else if (strNode.equals("content")) {
String src = xml.getAttributeValue(null, "src");
if (src != null) {
setStartUrl(src);
}
}
}
public void handleEndTag(XmlPullParser xml) {
String strNode = xml.getName();
if (strNode.equals("feature")) {
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
service = "";
pluginClass = "";
insideFeature = false;
onload = false;
}
}
private void setStartUrl(String src) {
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(src);
if (matcher.find()) {
launchUrl = src;
} else {
if (src.charAt(0) == '/') {
src = src.substring(1);
}
launchUrl = "file:///android_asset/www/" + src;
}
}
}

View File

@ -0,0 +1,519 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.ArrayList;
import java.util.Locale;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
/**
* This class is the main Android activity that represents the Cordova
* application. It should be extended by the user to load the specific
* html file that contains the application.
*
* As an example:
*
* <pre>
* package org.apache.cordova.examples;
*
* import android.os.Bundle;
* import org.apache.cordova.*;
*
* public class Example extends CordovaActivity {
* &#64;Override
* public void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
* super.init();
* // Load your application
* loadUrl(launchUrl);
* }
* }
* </pre>
*
* Cordova xml configuration: Cordova uses a configuration file at
* res/xml/config.xml to specify its settings. See "The config.xml File"
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
* for the configuration. The use of the set*Property() methods is
* deprecated in favor of the config.xml file.
*
*/
public class CordovaActivity extends Activity {
public static String TAG = "CordovaActivity";
// The webview for our app
protected CordovaWebView appView;
private static int ACTIVITY_STARTING = 0;
private static int ACTIVITY_RUNNING = 1;
private static int ACTIVITY_EXITING = 2;
// Keep app running when pause is received. (default = true)
// If true, then the JavaScript and native code continue to run in the background
// when another application (activity) is started.
protected boolean keepRunning = true;
// Flag to keep immersive mode if set to fullscreen
protected boolean immersiveMode;
// Read from config.xml:
protected CordovaPreferences preferences;
protected String launchUrl;
protected ArrayList<PluginEntry> pluginEntries;
protected CordovaInterfaceImpl cordovaInterface;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
loadConfig();
String logLevel = preferences.getString("loglevel", "ERROR");
LOG.setLogLevel(logLevel);
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
LOG.d(TAG, "CordovaActivity.onCreate()");
if (!preferences.getBoolean("ShowTitle", false)) {
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
}
if (preferences.getBoolean("SetFullscreen", false)) {
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
preferences.set("Fullscreen", true);
}
if (preferences.getBoolean("Fullscreen", false)) {
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
// (as was the case in previous cordova versions)
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) && !preferences.getBoolean("FullscreenNotImmersive", false)) {
immersiveMode = true;
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
}
super.onCreate(savedInstanceState);
cordovaInterface = makeCordovaInterface();
if (savedInstanceState != null) {
cordovaInterface.restoreInstanceState(savedInstanceState);
}
}
protected void init() {
appView = makeWebView();
createViews();
if (!appView.isInitialized()) {
appView.init(cordovaInterface, pluginEntries, preferences);
}
cordovaInterface.onCordovaInit(appView.getPluginManager());
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
}
@SuppressWarnings("deprecation")
protected void loadConfig() {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(this);
preferences = parser.getPreferences();
preferences.setPreferencesBundle(getIntent().getExtras());
launchUrl = parser.getLaunchUrl();
pluginEntries = parser.getPluginEntries();
Config.parser = parser;
}
//Suppressing warnings in AndroidStudio
@SuppressWarnings({"deprecation", "ResourceType"})
protected void createViews() {
//Why are we setting a constant as the ID? This should be investigated
appView.getView().setId(100);
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(appView.getView());
if (preferences.contains("BackgroundColor")) {
try {
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
// Background of activity:
appView.getView().setBackgroundColor(backgroundColor);
}
catch (NumberFormatException e){
e.printStackTrace();
}
}
appView.getView().requestFocusFromTouch();
}
/**
* Construct the default web view object.
* <p/>
* Override this to customize the webview that is used.
*/
protected CordovaWebView makeWebView() {
return new CordovaWebViewImpl(makeWebViewEngine());
}
protected CordovaWebViewEngine makeWebViewEngine() {
return CordovaWebViewImpl.createEngine(this, preferences);
}
protected CordovaInterfaceImpl makeCordovaInterface() {
return new CordovaInterfaceImpl(this) {
@Override
public Object onMessage(String id, Object data) {
// Plumb this to CordovaActivity.onMessage for backwards compatibility
return CordovaActivity.this.onMessage(id, data);
}
};
}
/**
* Load the url into the webview.
*/
public void loadUrl(String url) {
if (appView == null) {
init();
}
// If keepRunning
this.keepRunning = preferences.getBoolean("KeepRunning", true);
appView.loadUrlIntoView(url, true);
}
/**
* Called when the system is about to start resuming a previous activity.
*/
@Override
protected void onPause() {
super.onPause();
LOG.d(TAG, "Paused the activity.");
if (this.appView != null) {
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
this.appView.handlePause(keepRunning);
}
}
/**
* Called when the activity receives a new intent
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//Forward to plugins
if (this.appView != null)
this.appView.onNewIntent(intent);
}
/**
* Called when the activity will start interacting with the user.
*/
@Override
protected void onResume() {
super.onResume();
LOG.d(TAG, "Resumed the activity.");
if (this.appView == null) {
return;
}
// Force window to have focus, so application always
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
this.getWindow().getDecorView().requestFocus();
this.appView.handleResume(this.keepRunning);
}
/**
* Called when the activity is no longer visible to the user.
*/
@Override
protected void onStop() {
super.onStop();
LOG.d(TAG, "Stopped the activity.");
if (this.appView == null) {
return;
}
this.appView.handleStop();
}
/**
* Called when the activity is becoming visible to the user.
*/
@Override
protected void onStart() {
super.onStart();
LOG.d(TAG, "Started the activity.");
if (this.appView == null) {
return;
}
this.appView.handleStart();
}
/**
* The final call you receive before your activity is destroyed.
*/
@Override
public void onDestroy() {
LOG.d(TAG, "CordovaActivity.onDestroy()");
super.onDestroy();
if (this.appView != null) {
appView.handleDestroy();
}
}
/**
* Called when view focus is changed
*/
@SuppressLint("InlinedApi")
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && immersiveMode) {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
}
}
@SuppressLint("NewApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
cordovaInterface.setActivityResultRequestCode(requestCode);
super.startActivityForResult(intent, requestCode, options);
}
/**
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
super.onActivityResult(requestCode, resultCode, intent);
cordovaInterface.onActivityResult(requestCode, resultCode, intent);
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
final CordovaActivity me = this;
// If errorUrl specified, then load it
final String errorUrl = preferences.getString("errorUrl", null);
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
// Load URL on UI thread
me.runOnUiThread(new Runnable() {
public void run() {
me.appView.showWebPage(errorUrl, false, true, null);
}
});
}
// If not, then display error dialog
else {
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
me.runOnUiThread(new Runnable() {
public void run() {
if (exit) {
me.appView.getView().setVisibility(View.GONE);
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
}
}
});
}
}
/**
* Display an error dialog and optionally exit application.
*/
public void displayError(final String title, final String message, final String button, final boolean exit) {
final CordovaActivity me = this;
me.runOnUiThread(new Runnable() {
public void run() {
try {
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
dlg.setMessage(message);
dlg.setTitle(title);
dlg.setCancelable(false);
dlg.setPositiveButton(button,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if (exit) {
finish();
}
}
});
dlg.create();
dlg.show();
} catch (Exception e) {
finish();
}
}
});
}
/*
* Hook in Cordova for menu plugins
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (appView != null) {
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (appView != null) {
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (appView != null) {
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
}
return true;
}
/**
* Called when a message is sent to plugin.
*
* @param id The message id
* @param data The message data
* @return Object or null
*/
public Object onMessage(String id, Object data) {
if ("onReceivedError".equals(id)) {
JSONObject d = (JSONObject) data;
try {
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
} catch (JSONException e) {
e.printStackTrace();
}
} else if ("exit".equals(id)) {
finish();
}
return null;
}
protected void onSaveInstanceState(Bundle outState) {
cordovaInterface.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param newConfig The new device configuration
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (this.appView == null) {
return;
}
PluginManager pm = this.appView.getPluginManager();
if (pm != null) {
pm.onConfigurationChanged(newConfig);
}
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults) {
try
{
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
}
catch (JSONException e)
{
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,113 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Base64;
public class CordovaArgs {
private JSONArray baseArgs;
public CordovaArgs(JSONArray args) {
this.baseArgs = args;
}
// Pass through the basics to the base args.
public Object get(int index) throws JSONException {
return baseArgs.get(index);
}
public boolean getBoolean(int index) throws JSONException {
return baseArgs.getBoolean(index);
}
public double getDouble(int index) throws JSONException {
return baseArgs.getDouble(index);
}
public int getInt(int index) throws JSONException {
return baseArgs.getInt(index);
}
public JSONArray getJSONArray(int index) throws JSONException {
return baseArgs.getJSONArray(index);
}
public JSONObject getJSONObject(int index) throws JSONException {
return baseArgs.getJSONObject(index);
}
public long getLong(int index) throws JSONException {
return baseArgs.getLong(index);
}
public String getString(int index) throws JSONException {
return baseArgs.getString(index);
}
public Object opt(int index) {
return baseArgs.opt(index);
}
public boolean optBoolean(int index) {
return baseArgs.optBoolean(index);
}
public double optDouble(int index) {
return baseArgs.optDouble(index);
}
public int optInt(int index) {
return baseArgs.optInt(index);
}
public JSONArray optJSONArray(int index) {
return baseArgs.optJSONArray(index);
}
public JSONObject optJSONObject(int index) {
return baseArgs.optJSONObject(index);
}
public long optLong(int index) {
return baseArgs.optLong(index);
}
public String optString(int index) {
return baseArgs.optString(index);
}
public boolean isNull(int index) {
return baseArgs.isNull(index);
}
// The interesting custom helpers.
public byte[] getArrayBuffer(int index) throws JSONException {
String encoded = baseArgs.getString(index);
return Base64.decode(encoded, Base64.DEFAULT);
}
}

View File

@ -0,0 +1,187 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.annotation.SuppressLint;
import java.security.SecureRandom;
import org.json.JSONArray;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
public class CordovaBridge {
private static final String LOG_TAG = "CordovaBridge";
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
if (!verifySecret("exec()", bridgeSecret)) {
return null;
}
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
if (arguments == null) {
return "@Null arguments.";
}
jsMessageQueue.setPaused(true);
try {
// Tell the resourceApi what thread the JS is running on.
CordovaResourceApi.jsThread = Thread.currentThread();
pluginManager.exec(service, action, callbackId, arguments);
String ret = null;
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
ret = jsMessageQueue.popAndEncode(false);
}
return ret;
} catch (Throwable e) {
e.printStackTrace();
return "";
} finally {
jsMessageQueue.setPaused(false);
}
}
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
return;
}
jsMessageQueue.setBridgeMode(value);
}
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
return null;
}
return jsMessageQueue.popAndEncode(fromOnlineEvent);
}
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
if (!jsMessageQueue.isBridgeEnabled()) {
if (bridgeSecret == -1) {
LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
} else {
LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
}
return false;
}
// Bridge secret wrong and bridge not due to it being from the previous page.
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
clearBridgeSecret();
throw new IllegalAccessException();
}
return true;
}
/** Called on page transitions */
void clearBridgeSecret() {
expectedBridgeSecret = -1;
}
public boolean isSecretEstablished() {
return expectedBridgeSecret != -1;
}
/** Called by cordova.js to initialize the bridge. */
//On old Androids SecureRandom isn't really secure, this is the least of your problems if
//you're running Android 4.3 and below in 2017
@SuppressLint("TrulyRandom")
int generateBridgeSecret() {
SecureRandom randGen = new SecureRandom();
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
return expectedBridgeSecret;
}
public void reset() {
jsMessageQueue.reset();
clearBridgeSecret();
}
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
int bridgeSecret = array.getInt(0);
String service = array.getString(1);
String action = array.getString(2);
String callbackId = array.getString(3);
String r = jsExec(bridgeSecret, service, action, callbackId, message);
return r == null ? "" : r;
} catch (JSONException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
// Sets the native->JS bridge mode.
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
try {
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
} catch (NumberFormatException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
// Polling for JavaScript messages
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
try {
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
return r == null ? "" : r;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
// Protect against random iframes being able to talk through the bridge.
// Trust only pages which the app would have been allowed to navigate to anyway.
if (pluginManager.shouldAllowBridgeAccess(origin)) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
jsMessageQueue.setBridgeMode(bridgeMode);
// Tell JS the bridge secret.
int secret = generateBridgeSecret();
return ""+secret;
} else {
LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
}
return "";
}
return null;
}
}

View File

@ -0,0 +1,105 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import android.annotation.SuppressLint;
import android.webkit.ClientCertRequest;
/**
* Implementation of the ICordovaClientCertRequest for Android WebView.
*
*/
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public CordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
@SuppressLint("NewApi")
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
@SuppressLint("NewApi")
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
@SuppressLint("NewApi")
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
@SuppressLint("NewApi")
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
@SuppressLint("NewApi")
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
@SuppressLint("NewApi")
public void ignore()
{
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
@SuppressLint("NewApi")
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
{
request.proceed(privateKey, chain);
}
}

View File

@ -0,0 +1,152 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.KeyEvent;
import android.widget.EditText;
/**
* Helper class for WebViews to implement prompt(), alert(), confirm() dialogs.
*/
public class CordovaDialogsHelper {
private final Context context;
private AlertDialog lastHandledDialog;
public CordovaDialogsHelper(Context context) {
this.context = context;
}
public void showAlert(String message, final Result result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
dlg.setMessage(message);
dlg.setTitle("Alert");
//Don't let alerts break the back button
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.gotResult(true, null);
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.gotResult(false, null);
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.gotResult(true, null);
return false;
}
else
return true;
}
});
lastHandledDialog = dlg.show();
}
public void showConfirm(String message, final Result result) {
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
dlg.setMessage(message);
dlg.setTitle("Confirm");
dlg.setCancelable(true);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.gotResult(true, null);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.gotResult(false, null);
}
});
dlg.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.gotResult(false, null);
}
});
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
//DO NOTHING
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK)
{
result.gotResult(false, null);
return false;
}
else
return true;
}
});
lastHandledDialog = dlg.show();
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*/
public void showPrompt(String message, String defaultValue, final Result result) {
// Returning false would also show a dialog, but the default one shows the origin (ugly).
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
dlg.setMessage(message);
final EditText input = new EditText(context);
if (defaultValue != null) {
input.setText(defaultValue);
}
dlg.setView(input);
dlg.setCancelable(false);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String userText = input.getText().toString();
result.gotResult(true, userText);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.gotResult(false, null);
}
});
lastHandledDialog = dlg.show();
}
public void destroyLastDialog(){
if (lastHandledDialog != null){
lastHandledDialog.cancel();
}
}
public interface Result {
public void gotResult(boolean success, String value);
}
}

View File

@ -0,0 +1,51 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.webkit.HttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
public CordovaHttpAuthHandler(HttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel () {
this.handler.cancel();
}
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed (String username, String password) {
this.handler.proceed(username, password);
}
}

View File

@ -0,0 +1,97 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import org.apache.cordova.CordovaPlugin;
import java.util.concurrent.ExecutorService;
/**
* The Activity interface that is implemented by CordovaActivity.
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
*/
public interface CordovaInterface {
/**
* Launch an activity for which you would like a result when it finished. When this activity exits,
* your onActivityResult() method will be called.
*
* @param command The command object
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
/**
* Set the plugin to be called when a sub-activity exits.
*
* @param plugin The plugin on which onActivityResult is to be called
*/
abstract public void setActivityResultCallback(CordovaPlugin plugin);
/**
* Get the Android activity.
*
* If a custom engine lives outside of the Activity's lifecycle the return value may be null.
*
* @return the Activity
*/
public abstract Activity getActivity();
/**
* Get the Android context.
*
* @return the Context
*/
public Context getContext();
/**
* Called when a message is sent to plugin.
*
* @param id The message id
* @param data The message data
* @return Object or null
*/
public Object onMessage(String id, Object data);
/**
* Returns a shared thread pool that can be used for background tasks.
*/
public ExecutorService getThreadPool();
/**
* Sends a permission request to the activity for one permission.
*/
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
/**
* Sends a permission request to the activity for a group of permissions
*/
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
/**
* Check for a permission. Returns true if the permission is granted, false otherwise.
*/
public boolean hasPermission(String permission);
}

View File

@ -0,0 +1,249 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Default implementation of CordovaInterface.
*/
public class CordovaInterfaceImpl implements CordovaInterface {
private static final String TAG = "CordovaInterfaceImpl";
protected Activity activity;
protected ExecutorService threadPool;
protected PluginManager pluginManager;
protected ActivityResultHolder savedResult;
protected CallbackMap permissionResultCallbacks;
protected CordovaPlugin activityResultCallback;
protected String initCallbackService;
protected int activityResultRequestCode;
protected boolean activityWasDestroyed = false;
protected Bundle savedPluginState;
public CordovaInterfaceImpl(Activity activity) {
this(activity, Executors.newCachedThreadPool());
}
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
this.activity = activity;
this.threadPool = threadPool;
this.permissionResultCallbacks = new CallbackMap();
}
@Override
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
setActivityResultCallback(command);
try {
activity.startActivityForResult(intent, requestCode);
} catch (RuntimeException e) { // E.g.: ActivityNotFoundException
activityResultCallback = null;
throw e;
}
}
@Override
public void setActivityResultCallback(CordovaPlugin plugin) {
// Cancel any previously pending activity.
if (activityResultCallback != null) {
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
}
activityResultCallback = plugin;
}
@Override
public Activity getActivity() {
return activity;
}
@Override
public Context getContext() {
return activity;
}
@Override
public Object onMessage(String id, Object data) {
if ("exit".equals(id)) {
activity.finish();
}
return null;
}
@Override
public ExecutorService getThreadPool() {
return threadPool;
}
/**
* Dispatches any pending onActivityResult callbacks and sends the resume event if the
* Activity was destroyed by the OS.
*/
public void onCordovaInit(PluginManager pluginManager) {
this.pluginManager = pluginManager;
if (savedResult != null) {
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
} else if(activityWasDestroyed) {
// If there was no Activity result, we still need to send out the resume event if the
// Activity was destroyed by the OS
activityWasDestroyed = false;
if(pluginManager != null)
{
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
if(appPlugin != null) {
JSONObject obj = new JSONObject();
try {
obj.put("action", "resume");
} catch (JSONException e) {
LOG.e(TAG, "Failed to create event message", e);
}
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
}
}
}
}
/**
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
*/
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
CordovaPlugin callback = activityResultCallback;
if(callback == null && initCallbackService != null) {
// The application was restarted, but had defined an initial callback
// before being shut down.
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
if (pluginManager != null) {
callback = pluginManager.getPlugin(initCallbackService);
if(callback != null) {
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
new ResumeCallback(callback.getServiceName(), pluginManager));
}
}
}
activityResultCallback = null;
if (callback != null) {
LOG.d(TAG, "Sending activity result to plugin");
initCallbackService = null;
savedResult = null;
callback.onActivityResult(requestCode, resultCode, intent);
return true;
}
LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
return false;
}
/**
* Call this from your startActivityForResult() overload. This is required to catch the case
* where plugins use Activity.startActivityForResult() + CordovaInterface.setActivityResultCallback()
* rather than CordovaInterface.startActivityForResult().
*/
public void setActivityResultRequestCode(int requestCode) {
activityResultRequestCode = requestCode;
}
/**
* Saves parameters for startActivityForResult().
*/
public void onSaveInstanceState(Bundle outState) {
if (activityResultCallback != null) {
String serviceName = activityResultCallback.getServiceName();
outState.putString("callbackService", serviceName);
}
if(pluginManager != null){
outState.putBundle("plugin", pluginManager.onSaveInstanceState());
}
}
/**
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
*/
public void restoreInstanceState(Bundle savedInstanceState) {
initCallbackService = savedInstanceState.getString("callbackService");
savedPluginState = savedInstanceState.getBundle("plugin");
activityWasDestroyed = true;
}
private static class ActivityResultHolder {
private int requestCode;
private int resultCode;
private Intent intent;
public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
this.requestCode = requestCode;
this.resultCode = resultCode;
this.intent = intent;
}
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
if(callback != null) {
callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
}
}
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
String[] permissions = new String [1];
permissions[0] = permission;
requestPermissions(plugin, requestCode, permissions);
}
@SuppressLint("NewApi")
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
getActivity().requestPermissions(permissions, mappedRequestCode);
}
public boolean hasPermission(String permission)
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
int result = activity.checkSelfPermission(permission);
return PackageManager.PERMISSION_GRANTED == result;
}
else
{
return true;
}
}
}

View File

@ -0,0 +1,422 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Plugins must extend this class and override one of the execute methods.
*/
public class CordovaPlugin {
public CordovaWebView webView;
public CordovaInterface cordova;
protected CordovaPreferences preferences;
private String serviceName;
/**
* Call this after constructing to initialize the plugin.
* Final because we want to be able to change args without breaking plugins.
*/
public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
assert this.cordova == null;
this.serviceName = serviceName;
this.cordova = cordova;
this.webView = webView;
this.preferences = preferences;
initialize(cordova, webView);
pluginInitialize();
}
/**
* Called after plugin construction and fields have been initialized.
* Prefer to use pluginInitialize instead since there is no value in
* having parameters on the initialize() function.
*/
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
}
/**
* Called after plugin construction and fields have been initialized.
*/
protected void pluginInitialize() {
}
/**
* Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
*/
public String getServiceName() {
return serviceName;
}
/**
* Executes the request.
*
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param rawArgs The exec() arguments in JSON form.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
JSONArray args = new JSONArray(rawArgs);
return execute(action, args, callbackContext);
}
/**
* Executes the request.
*
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param args The exec() arguments.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
CordovaArgs cordovaArgs = new CordovaArgs(args);
return execute(action, cordovaArgs, callbackContext);
}
/**
* Executes the request.
*
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param args The exec() arguments, wrapped with some Cordova helpers.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*/
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
return false;
}
/**
* Called when the system is about to start resuming a previous activity.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
}
/**
* Called when the activity will start interacting with the user.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
}
/**
* Called when the activity is becoming visible to the user.
*/
public void onStart() {
}
/**
* Called when the activity is no longer visible to the user.
*/
public void onStop() {
}
/**
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
}
/**
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
}
/**
* Called when the Activity is being destroyed (e.g. if a plugin calls out to an external
* Activity and the OS kills the CordovaActivity in the background). The plugin should save its
* state in this method only if it is awaiting the result of an external Activity and needs
* to preserve some information so as to handle that result; onRestoreStateForActivityResult()
* will only be called if the plugin is the recipient of an Activity result
*
* @return Bundle containing the state of the plugin or null if state does not need to be saved
*/
public Bundle onSaveInstanceState() {
return null;
}
/**
* Called when a plugin is the recipient of an Activity result after the CordovaActivity has
* been destroyed. The Bundle will be the same as the one the plugin returned in
* onSaveInstanceState()
*
* @param state Bundle containing the state of the plugin
* @param callbackContext Replacement Context to return the plugin result to
*/
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
/**
* Called when a message is sent to plugin.
*
* @param id The message id
* @param data The message data
* @return Object to stop propagation or null
*/
public Object onMessage(String id, Object data) {
return null;
}
/**
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be
* attached to Intent "extras").
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
}
/**
* Hook for blocking the loading of external resources.
*
* This will be called when the WebView's shouldInterceptRequest wants to
* know whether to open a connection to an external resource. Return false
* to block the request: if any plugin returns false, Cordova will block
* the request. If all plugins return null, the default policy will be
* enforced. If at least one plugin returns true, and no plugins return
* false, then the request will proceed.
*
* Note that this only affects resource requests which are routed through
* WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
* img tag loads. WebSockets and media requests (such as <video> and <audio>
* tags) are not affected by this method. Use CSP headers to control access
* to such resources.
*/
public Boolean shouldAllowRequest(String url) {
return null;
}
/**
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
* iframe navigations.
*
* This will be called when the WebView's needs to know whether to navigate
* to a new page. Return false to block the navigation: if any plugin
* returns false, Cordova will block the navigation. If all plugins return
* null, the default policy will be enforced. It at least one plugin returns
* true, and no plugins return false, then the navigation will proceed.
*/
public Boolean shouldAllowNavigation(String url) {
return null;
}
/**
* Hook for allowing page to call exec(). By default, this returns the result of
* shouldAllowNavigation(). It's generally unsafe to allow untrusted content to be loaded
* into a CordovaWebView, even within an iframe, so it's best not to touch this.
*/
public Boolean shouldAllowBridgeAccess(String url) {
return shouldAllowNavigation(url);
}
/**
* Hook for blocking the launching of Intents by the Cordova application.
*
* This will be called when the WebView will not navigate to a page, but
* could launch an intent to handle the URL. Return false to block this: if
* any plugin returns false, Cordova will block the navigation. If all
* plugins return null, the default policy will be enforced. If at least one
* plugin returns true, and no plugins return false, then the URL will be
* opened.
*/
public Boolean shouldOpenExternalUrl(String url) {
return null;
}
/**
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
*
* @param url The URL that is trying to be loaded in the Cordova webview.
* @return Return true to prevent the URL from loading. Default is false.
*/
public boolean onOverrideUrlLoading(String url) {
return false;
}
/**
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
* To handle the request directly, return a URI in the form:
*
* cdvplugin://pluginId/...
*
* And implement handleOpenForRead().
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
*
* public Uri remapUri(Uri uri) { return toPluginUri(uri); }
*
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
* Uri origUri = fromPluginUri(uri);
* ...
* }
*/
public Uri remapUri(Uri uri) {
return null;
}
/**
* Called to handle CordovaResourceApi.openForRead() calls for a cdvplugin://pluginId/ URL.
* Should never return null.
* Added in cordova-android@4.0.0
*/
public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
throw new FileNotFoundException("Plugin can't handle uri: " + uri);
}
/**
* Refer to remapUri()
* Added in cordova-android@4.0.0
*/
protected Uri toPluginUri(Uri origUri) {
return new Uri.Builder()
.scheme(CordovaResourceApi.PLUGIN_URI_SCHEME)
.authority(serviceName)
.appendQueryParameter("origUri", origUri.toString())
.build();
}
/**
* Refer to remapUri()
* Added in cordova-android@4.0.0
*/
protected Uri fromPluginUri(Uri pluginUri) {
return Uri.parse(pluginUri.getQueryParameter("origUri"));
}
/**
* Called when the WebView does a top-level navigation or refreshes.
*
* Plugins should stop any long-running processes and clean up internal state.
*
* Does nothing by default.
*/
public void onReset() {
}
/**
* Called when the system received an HTTP authentication request. Plugin can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
return false;
}
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param newConfig The new device configuration
*/
public void onConfigurationChanged(Configuration newConfig) {
}
/**
* Called by the Plugin Manager when we need to actually request permissions
*
* @param requestCode Passed to the activity to track the request
*
* @return Returns the permission that was stored in the plugin
*/
public void requestPermissions(int requestCode) {
}
/*
* Called by the WebView implementation to check for geolocation permissions, can be used
* by other Java methods in the event that a plugin is using this as a dependency.
*
* @return Returns true if the plugin has all the permissions it needs to operate.
*/
public boolean hasPermisssion() {
return true;
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
}
}

View File

@ -0,0 +1,101 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.cordova.LOG;
import android.app.Activity;
import android.os.Bundle;
public class CordovaPreferences {
private HashMap<String, String> prefs = new HashMap<String, String>(20);
private Bundle preferencesBundleExtras;
public void setPreferencesBundle(Bundle extras) {
preferencesBundleExtras = extras;
}
public void set(String name, String value) {
prefs.put(name.toLowerCase(Locale.ENGLISH), value);
}
public void set(String name, boolean value) {
set(name, "" + value);
}
public void set(String name, int value) {
set(name, "" + value);
}
public void set(String name, double value) {
set(name, "" + value);
}
public Map<String, String> getAll() {
return prefs;
}
public boolean getBoolean(String name, boolean defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
// Added in 4.0.0
public boolean contains(String name) {
return getString(name, null) != null;
}
public int getInteger(String name, int defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
// Use Integer.decode() can't handle it if the highest bit is set.
return (int)(long)Long.decode(value);
}
return defaultValue;
}
public double getDouble(String name, double defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return Double.valueOf(value);
}
return defaultValue;
}
public String getString(String name, String defaultValue) {
name = name.toLowerCase(Locale.ENGLISH);
String value = prefs.get(name);
if (value != null) {
return value;
}
return defaultValue;
}
}

View File

@ -0,0 +1,471 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Looper;
import android.util.Base64;
import android.webkit.MimeTypeMap;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.Locale;
/**
* What this class provides:
* 1. Helpers for reading & writing to URLs.
* - E.g. handles assets, resources, content providers, files, data URIs, http[s]
* - E.g. Can be used to query for mime-type & content length.
*
* 2. To allow plugins to redirect URLs (via remapUrl).
* - All plugins should call remapUrl() on URLs they receive from JS *before*
* passing the URL onto other utility functions in this class.
* - For an example usage of this, refer to the org.apache.cordova.file plugin.
*
* Future Work:
* - Consider using a Cursor to query content URLs for their size (like the file plugin does).
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
* for large payloads.
*/
public class CordovaResourceApi {
@SuppressWarnings("unused")
private static final String LOG_TAG = "CordovaResourceApi";
public static final int URI_TYPE_FILE = 0;
public static final int URI_TYPE_ASSET = 1;
public static final int URI_TYPE_CONTENT = 2;
public static final int URI_TYPE_RESOURCE = 3;
public static final int URI_TYPE_DATA = 4;
public static final int URI_TYPE_HTTP = 5;
public static final int URI_TYPE_HTTPS = 6;
public static final int URI_TYPE_PLUGIN = 7;
public static final int URI_TYPE_UNKNOWN = -1;
public static final String PLUGIN_URI_SCHEME = "cdvplugin";
private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
public static Thread jsThread;
private final AssetManager assetManager;
private final ContentResolver contentResolver;
private final PluginManager pluginManager;
private boolean threadCheckingEnabled = true;
public CordovaResourceApi(Context context, PluginManager pluginManager) {
this.contentResolver = context.getContentResolver();
this.assetManager = context.getAssets();
this.pluginManager = pluginManager;
}
public void setThreadCheckingEnabled(boolean value) {
threadCheckingEnabled = value;
}
public boolean isThreadCheckingEnabled() {
return threadCheckingEnabled;
}
public static int getUriType(Uri uri) {
assertNonRelative(uri);
String scheme = uri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
return URI_TYPE_CONTENT;
}
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
return URI_TYPE_RESOURCE;
}
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
if (uri.getPath().startsWith("/android_asset/")) {
return URI_TYPE_ASSET;
}
return URI_TYPE_FILE;
}
if ("data".equalsIgnoreCase(scheme)) {
return URI_TYPE_DATA;
}
if ("http".equalsIgnoreCase(scheme)) {
return URI_TYPE_HTTP;
}
if ("https".equalsIgnoreCase(scheme)) {
return URI_TYPE_HTTPS;
}
if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
return URI_TYPE_PLUGIN;
}
return URI_TYPE_UNKNOWN;
}
public Uri remapUri(Uri uri) {
assertNonRelative(uri);
Uri pluginUri = pluginManager.remapUri(uri);
return pluginUri != null ? pluginUri : uri;
}
public String remapPath(String path) {
return remapUri(Uri.fromFile(new File(path))).getPath();
}
/**
* Returns a File that points to the resource, or null if the resource
* is not on the local filesystem.
*/
public File mapUriToFile(Uri uri) {
assertBackgroundThread();
switch (getUriType(uri)) {
case URI_TYPE_FILE:
return new File(uri.getPath());
case URI_TYPE_CONTENT: {
Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
if (cursor != null) {
try {
int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
if (columnIndex != -1 && cursor.getCount() > 0) {
cursor.moveToFirst();
String realPath = cursor.getString(columnIndex);
if (realPath != null) {
return new File(realPath);
}
}
} finally {
cursor.close();
}
}
}
}
return null;
}
public String getMimeType(Uri uri) {
switch (getUriType(uri)) {
case URI_TYPE_FILE:
case URI_TYPE_ASSET:
return getMimeTypeFromPath(uri.getPath());
case URI_TYPE_CONTENT:
case URI_TYPE_RESOURCE:
return contentResolver.getType(uri);
case URI_TYPE_DATA: {
return getDataUriMimeType(uri);
}
case URI_TYPE_HTTP:
case URI_TYPE_HTTPS: {
try {
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
conn.setDoInput(false);
conn.setRequestMethod("HEAD");
String mimeType = conn.getHeaderField("Content-Type");
if (mimeType != null) {
mimeType = mimeType.split(";")[0];
}
return mimeType;
} catch (IOException e) {
}
}
}
return null;
}
//This already exists
private String getMimeTypeFromPath(String path) {
String extension = path;
int lastDot = extension.lastIndexOf('.');
if (lastDot != -1) {
extension = extension.substring(lastDot + 1);
}
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
extension = extension.toLowerCase(Locale.getDefault());
if (extension.equals("3ga")) {
return "audio/3gpp";
} else if (extension.equals("js")) {
// Missing from the map :(.
return "text/javascript";
}
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
/**
* Opens a stream to the given URI, also providing the MIME type & length.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.
* @throws Throws an IOException if the URI cannot be opened.
* @throws Throws an IllegalStateException if called on a foreground thread.
*/
public OpenForReadResult openForRead(Uri uri) throws IOException {
return openForRead(uri, false);
}
/**
* Opens a stream to the given URI, also providing the MIME type & length.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.
* @throws Throws an IOException if the URI cannot be opened.
* @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false.
*/
public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
if (!skipThreadCheck) {
assertBackgroundThread();
}
switch (getUriType(uri)) {
case URI_TYPE_FILE: {
FileInputStream inputStream = new FileInputStream(uri.getPath());
String mimeType = getMimeTypeFromPath(uri.getPath());
long length = inputStream.getChannel().size();
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
}
case URI_TYPE_ASSET: {
String assetPath = uri.getPath().substring(15);
AssetFileDescriptor assetFd = null;
InputStream inputStream;
long length = -1;
try {
assetFd = assetManager.openFd(assetPath);
inputStream = assetFd.createInputStream();
length = assetFd.getLength();
} catch (FileNotFoundException e) {
// Will occur if the file is compressed.
inputStream = assetManager.open(assetPath);
}
String mimeType = getMimeTypeFromPath(assetPath);
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
}
case URI_TYPE_CONTENT:
case URI_TYPE_RESOURCE: {
String mimeType = contentResolver.getType(uri);
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
InputStream inputStream = assetFd.createInputStream();
long length = assetFd.getLength();
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
}
case URI_TYPE_DATA: {
OpenForReadResult ret = readDataUri(uri);
if (ret == null) {
break;
}
return ret;
}
case URI_TYPE_HTTP:
case URI_TYPE_HTTPS: {
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
conn.setDoInput(true);
String mimeType = conn.getHeaderField("Content-Type");
if (mimeType != null) {
mimeType = mimeType.split(";")[0];
}
int length = conn.getContentLength();
InputStream inputStream = conn.getInputStream();
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
}
case URI_TYPE_PLUGIN: {
String pluginId = uri.getHost();
CordovaPlugin plugin = pluginManager.getPlugin(pluginId);
if (plugin == null) {
throw new FileNotFoundException("Invalid plugin ID in URI: " + uri);
}
return plugin.handleOpenForRead(uri);
}
}
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
}
public OutputStream openOutputStream(Uri uri) throws IOException {
return openOutputStream(uri, false);
}
/**
* Opens a stream to the given URI.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.
* @throws Throws an IOException if the URI cannot be opened.
*/
public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
assertBackgroundThread();
switch (getUriType(uri)) {
case URI_TYPE_FILE: {
File localFile = new File(uri.getPath());
File parent = localFile.getParentFile();
if (parent != null) {
parent.mkdirs();
}
return new FileOutputStream(localFile, append);
}
case URI_TYPE_CONTENT:
case URI_TYPE_RESOURCE: {
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
return assetFd.createOutputStream();
}
}
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
}
public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
assertBackgroundThread();
return (HttpURLConnection)new URL(uri.toString()).openConnection();
}
// Copies the input to the output in the most efficient manner possible.
// Closes both streams.
public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
assertBackgroundThread();
try {
InputStream inputStream = input.inputStream;
if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
long offset = 0;
long length = input.length;
if (input.assetFd != null) {
offset = input.assetFd.getStartOffset();
}
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
// position first.
inChannel.position(offset);
outChannel.transferFrom(inChannel, 0, length);
} else {
final int BUFFER_SIZE = 8192;
byte[] buffer = new byte[BUFFER_SIZE];
for (;;) {
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
if (bytesRead <= 0) {
break;
}
outputStream.write(buffer, 0, bytesRead);
}
}
} finally {
input.inputStream.close();
if (outputStream != null) {
outputStream.close();
}
}
}
public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
copyResource(openForRead(sourceUri), outputStream);
}
// Added in 3.5.0.
public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {
copyResource(openForRead(sourceUri), openOutputStream(dstUri));
}
private void assertBackgroundThread() {
if (threadCheckingEnabled) {
Thread curThread = Thread.currentThread();
if (curThread == Looper.getMainLooper().getThread()) {
throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
}
if (curThread == jsThread) {
throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
}
}
}
private String getDataUriMimeType(Uri uri) {
String uriAsString = uri.getSchemeSpecificPart();
int commaPos = uriAsString.indexOf(',');
if (commaPos == -1) {
return null;
}
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
if (mimeParts.length > 0) {
return mimeParts[0];
}
return null;
}
private OpenForReadResult readDataUri(Uri uri) {
String uriAsString = uri.getSchemeSpecificPart();
int commaPos = uriAsString.indexOf(',');
if (commaPos == -1) {
return null;
}
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
String contentType = null;
boolean base64 = false;
if (mimeParts.length > 0) {
contentType = mimeParts[0];
}
for (int i = 1; i < mimeParts.length; ++i) {
if ("base64".equalsIgnoreCase(mimeParts[i])) {
base64 = true;
}
}
String dataPartAsString = uriAsString.substring(commaPos + 1);
byte[] data;
if (base64) {
data = Base64.decode(dataPartAsString, Base64.DEFAULT);
} else {
try {
data = dataPartAsString.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
data = dataPartAsString.getBytes();
}
}
InputStream inputStream = new ByteArrayInputStream(data);
return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
}
private static void assertNonRelative(Uri uri) {
if (!uri.isAbsolute()) {
throw new IllegalArgumentException("Relative URIs are not supported.");
}
}
public static final class OpenForReadResult {
public final Uri uri;
public final InputStream inputStream;
public final String mimeType;
public final long length;
public final AssetFileDescriptor assetFd;
public OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
this.uri = uri;
this.inputStream = inputStream;
this.mimeType = mimeType;
this.length = length;
this.assetFd = assetFd;
}
}
}

View File

@ -0,0 +1,142 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.webkit.WebChromeClient.CustomViewCallback;
/**
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
* This is an interface so that it can be easily mocked in tests.
* Methods may be added to this interface without a major version bump, as plugins & embedders
* are not expected to implement it.
*/
public interface CordovaWebView {
public static final String CORDOVA_VERSION = "7.0.0";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
boolean isInitialized();
View getView();
void loadUrlIntoView(String url, boolean recreatePlugins);
void stopLoading();
boolean canGoBack();
void clearCache();
/** Use parameter-less overload */
@Deprecated
void clearCache(boolean b);
void clearHistory();
boolean backHistory();
void handlePause(boolean keepRunning);
void onNewIntent(Intent intent);
void handleResume(boolean keepRunning);
void handleStart();
void handleStop();
void handleDestroy();
/**
* Send JavaScript statement back to JavaScript.
*
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
* Instead of executing snippets of JS, you should use the exec bridge
* to create a Java->JS communication channel.
* To do this:
* 1. Within plugin.xml (to have your JS run before deviceready):
* <js-module><runs/></js-module>
* 2. Within your .js (call exec on start-up):
* require('cordova/channel').onCordovaReady.subscribe(function() {
* require('cordova/exec')(win, null, 'Plugin', 'method', []);
* function win(message) {
* ... process message from java here ...
* }
* });
* 3. Within your .java:
* PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
* dataResult.setKeepCallback(true);
* savedCallbackContext.sendPluginResult(dataResult);
*/
@Deprecated
void sendJavascript(String statememt);
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.
* @param clearHistory Clear the history stack, so new page becomes top of history
* @param params Parameters for new app
*/
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
/**
* Deprecated in 4.0.0. Use your own View-toggling logic.
*/
@Deprecated
boolean isCustomViewShowing();
/**
* Deprecated in 4.0.0. Use your own View-toggling logic.
*/
@Deprecated
void showCustomView(View view, CustomViewCallback callback);
/**
* Deprecated in 4.0.0. Use your own View-toggling logic.
*/
@Deprecated
void hideCustomView();
CordovaResourceApi getResourceApi();
void setButtonPlumbedToJs(int keyCode, boolean override);
boolean isButtonPlumbedToJs(int keyCode);
void sendPluginResult(PluginResult cr, String callbackId);
PluginManager getPluginManager();
CordovaWebViewEngine getEngine();
CordovaPreferences getPreferences();
ICordovaCookieManager getCookieManager();
String getUrl();
// TODO: Work on deleting these by removing refs from plugins.
Context getContext();
void loadUrl(String url);
Object postMessage(String id, Object data);
}

View File

@ -0,0 +1,85 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ValueCallback;
/**
* Interface for all Cordova engines.
* No methods will be added to this class (in order to be compatible with existing engines).
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
*/
public interface CordovaWebViewEngine {
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue);
CordovaWebView getCordovaWebView();
ICordovaCookieManager getCookieManager();
View getView();
void loadUrl(String url, boolean clearNavigationStack);
void stopLoading();
/** Return the currently loaded URL */
String getUrl();
void clearCache();
/** After calling clearHistory(), canGoBack() should be false. */
void clearHistory();
boolean canGoBack();
/** Returns whether a navigation occurred */
boolean goBack();
/** Pauses / resumes the WebView's event loop. */
void setPaused(boolean value);
/** Clean up all resources associated with the WebView. */
void destroy();
/** Add the evaulate Javascript method **/
void evaluateJavascript(String js, ValueCallback<String> callback);
/**
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
*/
public interface EngineView {
CordovaWebView getCordovaWebView();
}
/**
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
* Methods may be added in future cordova versions, but never removed.
*/
public interface Client {
Boolean onDispatchKeyEvent(KeyEvent event);
void clearLoadTimeoutTimer();
void onPageStarted(String newUrl);
void onReceivedError(int errorCode, String description, String failingUrl);
void onPageFinishedLoading(String url);
boolean onNavigationAttempt(String url);
}
}

View File

@ -0,0 +1,615 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.widget.FrameLayout;
import org.apache.cordova.engine.SystemWebViewEngine;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
* Class uses two-phase initialization. You must call init() before calling any other methods.
*/
public class CordovaWebViewImpl implements CordovaWebView {
public static final String TAG = "CordovaWebViewImpl";
private PluginManager pluginManager;
protected final CordovaWebViewEngine engine;
private CordovaInterface cordova;
// Flag to track that a loadUrl timeout occurred
private int loadUrlTimeout = 0;
private CordovaResourceApi resourceApi;
private CordovaPreferences preferences;
private CoreAndroid appPlugin;
private NativeToJsMessageQueue nativeToJsMessageQueue;
private EngineClient engineClient = new EngineClient();
private boolean hasPausedEver;
// The URL passed to loadUrl(), not necessarily the URL of the current page.
String loadedUrl;
/** custom view created by the browser (a video player for example) */
private View mCustomView;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
try {
Class<?> webViewClass = Class.forName(className);
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
} catch (Exception e) {
throw new RuntimeException("Failed to create webview. ", e);
}
}
public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) {
this.engine = cordovaWebViewEngine;
}
// Convenience method for when creating programmatically (not from Config.xml).
public void init(CordovaInterface cordova) {
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
}
@SuppressLint("Assert")
@Override
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
if (this.cordova != null) {
throw new IllegalStateException();
}
this.cordova = cordova;
this.preferences = preferences;
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
nativeToJsMessageQueue = new NativeToJsMessageQueue();
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
if (preferences.getBoolean("DisallowOverscroll", false)) {
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
}
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
// This isn't enforced by the compiler, so assert here.
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
pluginManager.init();
}
@Override
public boolean isInitialized() {
return cordova != null;
}
@Override
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
LOG.d(TAG, ">>> loadUrl(" + url + ")");
if (url.equals("about:blank") || url.startsWith("javascript:")) {
engine.loadUrl(url, false);
return;
}
recreatePlugins = recreatePlugins || (loadedUrl == null);
if (recreatePlugins) {
// Don't re-initialize on first load.
if (loadedUrl != null) {
appPlugin = null;
pluginManager.init();
}
loadedUrl = url;
}
// Create a timeout timer for loadUrl
final int currentLoadUrlTimeout = loadUrlTimeout;
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
// Timeout error method
final Runnable loadError = new Runnable() {
public void run() {
stopLoading();
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
// Handle other errors by passing them to the webview in JS
JSONObject data = new JSONObject();
try {
data.put("errorCode", -6);
data.put("description", "The connection to the server was unsuccessful.");
data.put("url", url);
} catch (JSONException e) {
// Will never happen.
}
pluginManager.postMessage("onReceivedError", data);
}
};
// Timeout timer method
final Runnable timeoutCheck = new Runnable() {
public void run() {
try {
synchronized (this) {
wait(loadUrlTimeoutValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// If timeout, then stop loading and handle error
if (loadUrlTimeout == currentLoadUrlTimeout) {
cordova.getActivity().runOnUiThread(loadError);
}
}
};
final boolean _recreatePlugins = recreatePlugins;
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (loadUrlTimeoutValue > 0) {
cordova.getThreadPool().execute(timeoutCheck);
}
engine.loadUrl(url, _recreatePlugins);
}
});
}
@Override
public void loadUrl(String url) {
loadUrlIntoView(url, true);
}
@Override
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory);
// If clearing history
if (clearHistory) {
engine.clearHistory();
}
// If loading into our webview
if (!openExternal) {
// Make sure url is in whitelist
if (pluginManager.shouldAllowNavigation(url)) {
// TODO: What about params?
// Load new URL
loadUrlIntoView(url, true);
} else {
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
}
}
if (!pluginManager.shouldOpenExternalUrl(url)) {
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
return;
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
Uri uri = Uri.parse(url);
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
} else {
intent.setData(uri);
}
cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
@Override
@Deprecated
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
LOG.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
// Store the view and its callback for later (to kill it properly)
mCustomView = view;
mCustomViewCallback = callback;
// Add the custom view to its container.
ViewGroup parent = (ViewGroup) engine.getView().getParent();
parent.addView(view, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER));
// Hide the content view.
engine.getView().setVisibility(View.GONE);
// Finally show the custom view container.
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
@Override
@Deprecated
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
if (mCustomView == null) return;
LOG.d(TAG, "Hiding Custom View");
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
// Remove the custom view from its container.
ViewGroup parent = (ViewGroup) engine.getView().getParent();
parent.removeView(mCustomView);
mCustomView = null;
mCustomViewCallback.onCustomViewHidden();
// Show the content view.
engine.getView().setVisibility(View.VISIBLE);
}
@Override
@Deprecated
public boolean isCustomViewShowing() {
return mCustomView != null;
}
@Override
@Deprecated
public void sendJavascript(String statement) {
nativeToJsMessageQueue.addJavaScript(statement);
}
@Override
public void sendPluginResult(PluginResult cr, String callbackId) {
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
}
@Override
public PluginManager getPluginManager() {
return pluginManager;
}
@Override
public CordovaPreferences getPreferences() {
return preferences;
}
@Override
public ICordovaCookieManager getCookieManager() {
return engine.getCookieManager();
}
@Override
public CordovaResourceApi getResourceApi() {
return resourceApi;
}
@Override
public CordovaWebViewEngine getEngine() {
return engine;
}
@Override
public View getView() {
return engine.getView();
}
@Override
public Context getContext() {
return engine.getView().getContext();
}
private void sendJavascriptEvent(String event) {
if (appPlugin == null) {
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
}
if (appPlugin == null) {
LOG.w(TAG, "Unable to fire event without existing plugin");
return;
}
appPlugin.fireJavascriptEvent(event);
}
@Override
public void setButtonPlumbedToJs(int keyCode, boolean override) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MENU:
// TODO: Why are search and menu buttons handled separately?
if (override) {
boundKeyCodes.add(keyCode);
} else {
boundKeyCodes.remove(keyCode);
}
return;
default:
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
}
}
@Override
public boolean isButtonPlumbedToJs(int keyCode) {
return boundKeyCodes.contains(keyCode);
}
@Override
public Object postMessage(String id, Object data) {
return pluginManager.postMessage(id, data);
}
// Engine method proxies:
@Override
public String getUrl() {
return engine.getUrl();
}
@Override
public void stopLoading() {
// Clear timeout flag
loadUrlTimeout++;
}
@Override
public boolean canGoBack() {
return engine.canGoBack();
}
@Override
public void clearCache() {
engine.clearCache();
}
@Override
@Deprecated
public void clearCache(boolean b) {
engine.clearCache();
}
@Override
public void clearHistory() {
engine.clearHistory();
}
@Override
public boolean backHistory() {
return engine.goBack();
}
/////// LifeCycle methods ///////
@Override
public void onNewIntent(Intent intent) {
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
@Override
public void handlePause(boolean keepRunning) {
if (!isInitialized()) {
return;
}
hasPausedEver = true;
pluginManager.onPause(keepRunning);
sendJavascriptEvent("pause");
// If app doesn't want to run in background
if (!keepRunning) {
// Pause JavaScript timers. This affects all webviews within the app!
engine.setPaused(true);
}
}
@Override
public void handleResume(boolean keepRunning) {
if (!isInitialized()) {
return;
}
// Resume JavaScript timers. This affects all webviews within the app!
engine.setPaused(false);
this.pluginManager.onResume(keepRunning);
// In order to match the behavior of the other platforms, we only send onResume after an
// onPause has occurred. The resume event might still be sent if the Activity was killed
// while waiting for the result of an external Activity once the result is obtained
if (hasPausedEver) {
sendJavascriptEvent("resume");
}
}
@Override
public void handleStart() {
if (!isInitialized()) {
return;
}
pluginManager.onStart();
}
@Override
public void handleStop() {
if (!isInitialized()) {
return;
}
pluginManager.onStop();
}
@Override
public void handleDestroy() {
if (!isInitialized()) {
return;
}
// Cancel pending timeout timer.
loadUrlTimeout++;
// Forward to plugins
this.pluginManager.onDestroy();
// TODO: about:blank is a bit special (and the default URL for new frames)
// We should use a blank data: url instead so it's more obvious
this.loadUrl("about:blank");
// TODO: Should not destroy webview until after about:blank is done loading.
engine.destroy();
hideCustomView();
}
protected class EngineClient implements CordovaWebViewEngine.Client {
@Override
public void clearLoadTimeoutTimer() {
loadUrlTimeout++;
}
@Override
public void onPageStarted(String newUrl) {
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
boundKeyCodes.clear();
pluginManager.onReset();
pluginManager.postMessage("onPageStarted", newUrl);
}
@Override
public void onReceivedError(int errorCode, String description, String failingUrl) {
clearLoadTimeoutTimer();
JSONObject data = new JSONObject();
try {
data.put("errorCode", errorCode);
data.put("description", description);
data.put("url", failingUrl);
} catch (JSONException e) {
e.printStackTrace();
}
pluginManager.postMessage("onReceivedError", data);
}
@Override
public void onPageFinishedLoading(String url) {
LOG.d(TAG, "onPageFinished(" + url + ")");
clearLoadTimeoutTimer();
// Broadcast message that page has loaded
pluginManager.postMessage("onPageFinished", url);
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
if (engine.getView().getVisibility() != View.VISIBLE) {
Thread t = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
pluginManager.postMessage("spinner", "stop");
}
});
} catch (InterruptedException e) {
}
}
});
t.start();
}
// Shutdown if blank loaded
if (url.equals("about:blank")) {
pluginManager.postMessage("exit", null);
}
}
@Override
public Boolean onDispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isBackButton && mCustomView != null) {
return true;
} else if (boundKeyCodes.contains(keyCode)) {
return true;
} else if (isBackButton) {
return engine.canGoBack();
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (isBackButton && mCustomView != null) {
hideCustomView();
return true;
} else if (boundKeyCodes.contains(keyCode)) {
String eventName = null;
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
eventName = "volumedownbutton";
break;
case KeyEvent.KEYCODE_VOLUME_UP:
eventName = "volumeupbutton";
break;
case KeyEvent.KEYCODE_SEARCH:
eventName = "searchbutton";
break;
case KeyEvent.KEYCODE_MENU:
eventName = "menubutton";
break;
case KeyEvent.KEYCODE_BACK:
eventName = "backbutton";
break;
}
if (eventName != null) {
sendJavascriptEvent(eventName);
return true;
}
} else if (isBackButton) {
return engine.goBack();
}
}
return null;
}
@Override
public boolean onNavigationAttempt(String url) {
// Give plugins the chance to handle the url
if (pluginManager.onOverrideUrlLoading(url)) {
return true;
} else if (pluginManager.shouldAllowNavigation(url)) {
return false;
} else if (pluginManager.shouldOpenExternalUrl(url)) {
showWebPage(url, true, false, null);
return true;
}
LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);
return true;
}
}
}

View File

@ -0,0 +1,390 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import java.lang.reflect.Field;
import java.util.HashMap;
/**
* This class exposes methods in Cordova that can be called from JavaScript.
*/
public class CoreAndroid extends CordovaPlugin {
public static final String PLUGIN_NAME = "CoreAndroid";
protected static final String TAG = "CordovaApp";
private BroadcastReceiver telephonyReceiver;
private CallbackContext messageChannel;
private PluginResult pendingResume;
private final Object messageChannelLock = new Object();
/**
* Send an event to be fired on the Javascript side.
*
* @param action The name of the event to be fired
*/
public void fireJavascriptEvent(String action) {
sendEventMessage(action);
}
/**
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*/
@Override
public void pluginInitialize() {
this.initTelephonyReceiver();
}
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context from which we were invoked.
* @return A PluginResult object with a status and message.
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("clearCache")) {
this.clearCache();
}
else if (action.equals("show")) {
// This gets called from JavaScript onCordovaReady to show the webview.
// I recommend we change the name of the Message as spinner/stop is not
// indicative of what this actually does (shows the webview).
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.getPluginManager().postMessage("spinner", "stop");
}
});
}
else if (action.equals("loadUrl")) {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
else if (action.equals("cancelLoadUrl")) {
//this.cancelLoadUrl();
}
else if (action.equals("clearHistory")) {
this.clearHistory();
}
else if (action.equals("backHistory")) {
this.backHistory();
}
else if (action.equals("overrideButton")) {
this.overrideButton(args.getString(0), args.getBoolean(1));
}
else if (action.equals("overrideBackbutton")) {
this.overrideBackbutton(args.getBoolean(0));
}
else if (action.equals("exitApp")) {
this.exitApp();
}
else if (action.equals("messageChannel")) {
synchronized(messageChannelLock) {
messageChannel = callbackContext;
if (pendingResume != null) {
sendEventMessage(pendingResume);
pendingResume = null;
}
}
return true;
}
callbackContext.sendPluginResult(new PluginResult(status, result));
return true;
} catch (JSONException e) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return false;
}
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
/**
* Clear the resource cache.
*/
public void clearCache() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.clearCache(true);
}
});
}
/**
* Load the url into the webview.
*
* @param url
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
* @throws JSONException
*/
public void loadUrl(String url, JSONObject props) throws JSONException {
LOG.d("App", "App.loadUrl("+url+","+props+")");
int wait = 0;
boolean openExternal = false;
boolean clearHistory = false;
// If there are properties, then set them on the Activity
HashMap<String, Object> params = new HashMap<String, Object>();
if (props != null) {
JSONArray keys = props.names();
for (int i = 0; i < keys.length(); i++) {
String key = keys.getString(i);
if (key.equals("wait")) {
wait = props.getInt(key);
}
else if (key.equalsIgnoreCase("openexternal")) {
openExternal = props.getBoolean(key);
}
else if (key.equalsIgnoreCase("clearhistory")) {
clearHistory = props.getBoolean(key);
}
else {
Object value = props.get(key);
if (value == null) {
}
else if (value.getClass().equals(String.class)) {
params.put(key, (String)value);
}
else if (value.getClass().equals(Boolean.class)) {
params.put(key, (Boolean)value);
}
else if (value.getClass().equals(Integer.class)) {
params.put(key, (Integer)value);
}
}
}
}
// If wait property, then delay loading
if (wait > 0) {
try {
synchronized(this) {
this.wait(wait);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.webView.showWebPage(url, openExternal, clearHistory, params);
}
/**
* Clear page history for the app.
*/
public void clearHistory() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.clearHistory();
}
});
}
/**
* Go to previous page displayed.
* This is the same as pressing the backbutton on Android device.
*/
public void backHistory() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
webView.backHistory();
}
});
}
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* @param override T=override, F=cancel override
*/
public void overrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
}
/**
* Override the default behavior of the Android volume buttons.
* If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
*
* @param button volumeup, volumedown
* @param override T=override, F=cancel override
*/
public void overrideButton(String button, boolean override) {
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
if (button.equals("volumeup")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
else if (button.equals("menubutton")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
}
}
/**
* Return whether the Android back button is overridden by the user.
*
* @return boolean
*/
public boolean isBackbuttonOverridden() {
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
}
/**
* Exit the Android application.
*/
public void exitApp() {
this.webView.getPluginManager().postMessage("exit", null);
}
/**
* Listen for telephony events: RINGING, OFFHOOK and IDLE
* Send these events to all plugins using
* CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
*/
private void initTelephonyReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
//final CordovaInterface mycordova = this.cordova;
this.telephonyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// If state has changed
if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
LOG.i(TAG, "Telephone RINGING");
webView.getPluginManager().postMessage("telephone", "ringing");
}
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
LOG.i(TAG, "Telephone OFFHOOK");
webView.getPluginManager().postMessage("telephone", "offhook");
}
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
LOG.i(TAG, "Telephone IDLE");
webView.getPluginManager().postMessage("telephone", "idle");
}
}
}
}
};
// Register the receiver
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
}
private void sendEventMessage(String action) {
JSONObject obj = new JSONObject();
try {
obj.put("action", action);
} catch (JSONException e) {
LOG.e(TAG, "Failed to create event message", e);
}
sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
}
private void sendEventMessage(PluginResult payload) {
payload.setKeepCallback(true);
if (messageChannel != null) {
messageChannel.sendPluginResult(payload);
}
}
/*
* Unregister the receiver
*
*/
public void onDestroy()
{
webView.getContext().unregisterReceiver(this.telephonyReceiver);
}
/**
* Used to send the resume event in the case that the Activity is destroyed by the OS
*
* @param resumeEvent PluginResult containing the payload for the resume event to be fired
*/
public void sendResumeEvent(PluginResult resumeEvent) {
// This operation must be synchronized because plugin results that trigger resume
// events can be processed asynchronously
synchronized(messageChannelLock) {
if (messageChannel != null) {
sendEventMessage(resumeEvent);
} else {
// Might get called before the page loads, so we need to store it until the
// messageChannel gets created
this.pendingResume = resumeEvent;
}
}
}
/*
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
* that read the Build Configuration.
*
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
* StackOverflow. This is annoying as hell!
*
*/
public static Object getBuildConfigValue(Context ctx, String key)
{
try
{
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
Field field = clazz.getField(key);
return field.get(null);
} catch (ClassNotFoundException e) {
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
e.printStackTrace();
} catch (NoSuchFieldException e) {
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
} catch (IllegalAccessException e) {
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,31 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONException;
/*
* Any exposed Javascript API MUST implement these three things!
*/
public interface ExposedJsApi {
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
}

View File

@ -0,0 +1,66 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Specifies interface for handling certificate requests.
*/
public interface ICordovaClientCertRequest {
/**
* Cancel this request
*/
public void cancel();
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost();
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes();
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort();
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals();
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore();
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain);
}

View File

@ -0,0 +1,33 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
public interface ICordovaCookieManager {
public void setCookiesEnabled(boolean accept);
public void setCookie(final String url, final String value);
public String getCookie(final String url);
public void clearCookies();
public void flush();
};

View File

@ -0,0 +1,38 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public interface ICordovaHttpAuthHandler {
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel ();
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username The user name
* @param password The password
*/
public void proceed (String username, String password);
}

View File

@ -0,0 +1,244 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.util.Log;
/**
* Log to Android logging system.
*
* Log message can be a string or a printf formatted string with arguments.
* See http://developer.android.com/reference/java/util/Formatter.html
*/
public class LOG {
public static final int VERBOSE = Log.VERBOSE;
public static final int DEBUG = Log.DEBUG;
public static final int INFO = Log.INFO;
public static final int WARN = Log.WARN;
public static final int ERROR = Log.ERROR;
// Current log level
public static int LOGLEVEL = Log.ERROR;
/**
* Set the current log level.
*
* @param logLevel
*/
public static void setLogLevel(int logLevel) {
LOGLEVEL = logLevel;
Log.i("CordovaLog", "Changing log level to " + logLevel);
}
/**
* Set the current log level.
*
* @param logLevel
*/
public static void setLogLevel(String logLevel) {
if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE;
else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG;
else if ("INFO".equals(logLevel)) LOGLEVEL = INFO;
else if ("WARN".equals(logLevel)) LOGLEVEL = WARN;
else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR;
Log.i("CordovaLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")");
}
/**
* Determine if log level will be logged
*
* @param logLevel
* @return true if the parameter passed in is greater than or equal to the current log level
*/
public static boolean isLoggable(int logLevel) {
return (logLevel >= LOGLEVEL);
}
/**
* Verbose log message.
*
* @param tag
* @param s
*/
public static void v(String tag, String s) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s);
}
/**
* Debug log message.
*
* @param tag
* @param s
*/
public static void d(String tag, String s) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s);
}
/**
* Info log message.
*
* @param tag
* @param s
*/
public static void i(String tag, String s) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s);
}
/**
* Warning log message.
*
* @param tag
* @param s
*/
public static void w(String tag, String s) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s);
}
/**
* Error log message.
*
* @param tag
* @param s
*/
public static void e(String tag, String s) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s);
}
/**
* Verbose log message.
*
* @param tag
* @param s
* @param e
*/
public static void v(String tag, String s, Throwable e) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e);
}
/**
* Debug log message.
*
* @param tag
* @param s
* @param e
*/
public static void d(String tag, String s, Throwable e) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e);
}
/**
* Info log message.
*
* @param tag
* @param s
* @param e
*/
public static void i(String tag, String s, Throwable e) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e);
}
/**
* Warning log message.
*
* @param tag
* @param e
*/
public static void w(String tag, Throwable e) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, e);
}
/**
* Warning log message.
*
* @param tag
* @param s
* @param e
*/
public static void w(String tag, String s, Throwable e) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e);
}
/**
* Error log message.
*
* @param tag
* @param s
* @param e
*/
public static void e(String tag, String s, Throwable e) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e);
}
/**
* Verbose log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void v(String tag, String s, Object... args) {
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args));
}
/**
* Debug log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void d(String tag, String s, Object... args) {
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args));
}
/**
* Info log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void i(String tag, String s, Object... args) {
if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args));
}
/**
* Warning log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void w(String tag, String s, Object... args) {
if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args));
}
/**
* Error log message with printf formatting.
*
* @param tag
* @param s
* @param args
*/
public static void e(String tag, String s, Object... args) {
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args));
}
}

View File

@ -0,0 +1,539 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* Holds the list of messages to be sent to the WebView.
*/
public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue";
// Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking).
// Doesn't work for multipart messages.
private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable sending back native->JS messages during an exec() when the active
// exec() is asynchronous. Set this to true when running bridge benchmarks.
static final boolean DISABLE_EXEC_CHAINING = false;
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
// This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
/**
* When true, the active listener is not fired upon enqueue. When set to false,
* the active listener will be fired if the queue is non-empty.
*/
private boolean paused;
/**
* The list of JavaScript statements to be sent to JavaScript.
*/
private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
/**
* The array of listeners that can be used to send messages to JS.
*/
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
/**
* When null, the bridge is disabled. This occurs during page transitions.
* When disabled, all callbacks are dropped since they are assumed to be
* relevant to the previous page.
*/
private BridgeMode activeBridgeMode;
public void addBridgeMode(BridgeMode bridgeMode) {
bridgeModes.add(bridgeMode);
}
public boolean isBridgeEnabled() {
return activeBridgeMode != null;
}
public boolean isEmpty() {
return queue.isEmpty();
}
/**
* Changes the bridge mode.
*/
public void setBridgeMode(int value) {
if (value < -1 || value >= bridgeModes.size()) {
LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else {
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
if (newMode != activeBridgeMode) {
LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
synchronized (this) {
activeBridgeMode = newMode;
if (newMode != null) {
newMode.reset();
if (!paused && !queue.isEmpty()) {
newMode.onNativeToJsMessageAvailable(this);
}
}
}
}
}
}
/**
* Clears all messages and resets to the default bridge mode.
*/
public void reset() {
synchronized (this) {
queue.clear();
setBridgeMode(-1);
}
}
private int calculatePackedMessageLength(JsMessage message) {
int messageLen = message.calculateEncodedLength();
String messageLenStr = String.valueOf(messageLen);
return messageLenStr.length() + messageLen + 1;
}
private void packMessage(JsMessage message, StringBuilder sb) {
int len = message.calculateEncodedLength();
sb.append(len)
.append(' ');
message.encodeAsMessage(sb);
}
/**
* Combines and returns queued messages combined into a single string.
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
* Returns null if the queue is empty.
*/
public String popAndEncode(boolean fromOnlineEvent) {
synchronized (this) {
if (activeBridgeMode == null) {
return null;
}
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
if (queue.isEmpty()) {
return null;
}
int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) {
int messageSize = calculatePackedMessageLength(message);
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
break;
}
totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
StringBuilder sb = new StringBuilder(totalPayloadLen);
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst();
packMessage(message, sb);
}
if (!queue.isEmpty()) {
// Attach a char to indicate that there are more messages pending.
sb.append('*');
}
String ret = sb.toString();
return ret;
}
}
/**
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
*/
public String popAndEncodeAsJs() {
synchronized (this) {
int length = queue.size();
if (length == 0) {
return null;
}
int totalPayloadLen = 0;
int numMessagesToSend = 0;
for (JsMessage message : queue) {
int messageSize = message.calculateEncodedLength() + 50; // overestimate.
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
break;
}
totalPayloadLen += messageSize;
numMessagesToSend += 1;
}
boolean willSendAllMessages = numMessagesToSend == queue.size();
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
// Wrap each statement in a try/finally so that if one throws it does
// not affect the next.
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst();
if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
message.encodeAsJsMessage(sb);
} else {
sb.append("try{");
message.encodeAsJsMessage(sb);
sb.append("}finally{");
}
}
if (!willSendAllMessages) {
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
}
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
sb.append('}');
}
String ret = sb.toString();
return ret;
}
}
/**
* Add a JavaScript statement to the list.
*/
public void addJavaScript(String statement) {
enqueueMessage(new JsMessage(statement));
}
/**
* Add a JavaScript statement to the list.
*/
public void addPluginResult(PluginResult result, String callbackId) {
if (callbackId == null) {
LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
return;
}
// Don't send anything if there is no result and there is no need to
// clear the callbacks.
boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
boolean keepCallback = result.getKeepCallback();
if (noResult && keepCallback) {
return;
}
JsMessage message = new JsMessage(result, callbackId);
if (FORCE_ENCODE_USING_EVAL) {
StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
message.encodeAsJsMessage(sb);
message = new JsMessage(sb.toString());
}
enqueueMessage(message);
}
private void enqueueMessage(JsMessage message) {
synchronized (this) {
if (activeBridgeMode == null) {
LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
return;
}
queue.add(message);
if (!paused) {
activeBridgeMode.onNativeToJsMessageAvailable(this);
}
}
}
public void setPaused(boolean value) {
if (paused && value) {
// This should never happen. If a use-case for it comes up, we should
// change pause to be a counter.
LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
}
paused = value;
if (!value) {
synchronized (this) {
if (!queue.isEmpty() && activeBridgeMode != null) {
activeBridgeMode.onNativeToJsMessageAvailable(this);
}
}
}
}
public static abstract class BridgeMode {
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
public void reset() {}
}
/** Uses JS polls for messages on a timer.. */
public static class NoOpBridgeMode extends BridgeMode {
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
}
}
/** Uses webView.loadUrl("javascript:") to execute messages. */
public static class LoadUrlBridgeMode extends BridgeMode {
private final CordovaWebViewEngine engine;
private final CordovaInterface cordova;
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
this.engine = engine;
this.cordova = cordova;
}
@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
String js = queue.popAndEncodeAsJs();
if (js != null) {
engine.loadUrl("javascript:" + js, false);
}
}
});
}
}
/** Uses online/offline events to tell the JS when to poll for messages. */
public static class OnlineEventsBridgeMode extends BridgeMode {
private final OnlineEventsBridgeModeDelegate delegate;
private boolean online;
private boolean ignoreNextFlush;
public interface OnlineEventsBridgeModeDelegate {
void setNetworkAvailable(boolean value);
void runOnUiThread(Runnable r);
}
public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) {
this.delegate = delegate;
}
@Override
public void reset() {
delegate.runOnUiThread(new Runnable() {
public void run() {
online = false;
// If the following call triggers a notifyOfFlush, then ignore it.
ignoreNextFlush = true;
delegate.setNetworkAvailable(true);
}
});
}
@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
delegate.runOnUiThread(new Runnable() {
public void run() {
if (!queue.isEmpty()) {
ignoreNextFlush = false;
delegate.setNetworkAvailable(online);
}
}
});
}
// Track when online/offline events are fired so that we don't fire excess events.
@Override
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
if (fromOnlineEvent && !ignoreNextFlush) {
online = !online;
}
}
}
/** Uses webView.evaluateJavascript to execute messages. */
public static class EvalBridgeMode extends BridgeMode {
private final CordovaWebViewEngine engine;
private final CordovaInterface cordova;
public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
this.engine = engine;
this.cordova = cordova;
}
@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
String js = queue.popAndEncodeAsJs();
if (js != null) {
engine.evaluateJavascript(js, null);
}
}
});
}
}
private static class JsMessage {
final String jsPayloadOrCallbackId;
final PluginResult pluginResult;
JsMessage(String js) {
if (js == null) {
throw new NullPointerException();
}
jsPayloadOrCallbackId = js;
pluginResult = null;
}
JsMessage(PluginResult pluginResult, String callbackId) {
if (callbackId == null || pluginResult == null) {
throw new NullPointerException();
}
jsPayloadOrCallbackId = callbackId;
this.pluginResult = pluginResult;
}
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
return 1;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_STRING: // s
return 1 + pluginResult.getStrMessage().length();
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_MULTIPART:
int ret = 1;
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
int argLength = String.valueOf(length).length();
ret += argLength + 1 + length;
}
return ret;
case PluginResult.MESSAGE_TYPE_JSON:
default:
return pluginResult.getMessage().length();
}
}
int calculateEncodedLength() {
if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1;
}
int statusLen = String.valueOf(pluginResult.getStatus()).length();
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
return ret + calculateEncodedLengthHelper(pluginResult);
}
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN:
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
break;
case PluginResult.MESSAGE_TYPE_NULL: // N
sb.append('N');
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
sb.append('n')
.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
sb.append('s');
sb.append(pluginResult.getStrMessage());
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
sb.append('S');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
sb.append('A');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_MULTIPART:
sb.append('M');
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
sb.append(' ');
encodeAsMessageHelper(sb, multipartMessage);
}
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
sb.append(pluginResult.getMessage()); // [ or {
}
}
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
encodeAsMessageHelper(sb, pluginResult);
}
void buildJsMessage(StringBuilder sb) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_MULTIPART:
int size = pluginResult.getMultipartMessagesSize();
for (int i=0; i<size; i++) {
PluginResult subresult = pluginResult.getMultipartMessage(i);
JsMessage submessage = new JsMessage(subresult, jsPayloadOrCallbackId);
submessage.buildJsMessage(sb);
if (i < (size-1)) {
sb.append(",");
}
}
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
sb.append("atob('")
.append(pluginResult.getMessage())
.append("')");
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
sb.append("cordova.require('cordova/base64').toArrayBuffer('")
.append(pluginResult.getMessage())
.append("')");
break;
default:
sb.append(pluginResult.getMessage());
}
}
void encodeAsJsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append(jsPayloadOrCallbackId);
} else {
int status = pluginResult.getStatus();
boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
sb.append("cordova.callbackFromNative('")
.append(jsPayloadOrCallbackId)
.append("',")
.append(success)
.append(",")
.append(status)
.append(",[");
buildJsMessage(sb);
sb.append("],")
.append(pluginResult.getKeepCallback())
.append(");");
}
}
}
}

View File

@ -0,0 +1,87 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.Arrays;
import org.json.JSONException;
import android.content.pm.PackageManager;
/**
* This class provides reflective methods for permission requesting and checking so that plugins
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
*/
public class PermissionHelper {
private static final String LOG_TAG = "CordovaPermissionHelper";
/**
* Requests a "dangerous" permission for the application at runtime. This is a helper method
* alternative to cordovaInterface.requestPermission() that does not require the project to be
* built with cordova-android 5.0.0+
*
* @param plugin The plugin the permission is being requested for
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
* along with the result of the permission request
* @param permission The permission to be requested
*/
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
}
/**
* Requests "dangerous" permissions for the application at runtime. This is a helper method
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
* built with cordova-android 5.0.0+
*
* @param plugin The plugin the permissions are being requested for
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
* along with the result of the permissions request
* @param permissions The permissions to be requested
*/
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
plugin.cordova.requestPermissions(plugin, requestCode, permissions);
}
/**
* Checks at runtime to see if the application has been granted a permission. This is a helper
* method alternative to cordovaInterface.hasPermission() that does not require the project to
* be built with cordova-android 5.0.0+
*
* @param plugin The plugin the permission is being checked against
* @param permission The permission to be checked
*
* @return True if the permission has already been granted and false otherwise
*/
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
return plugin.cordova.hasPermission(permission);
}
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
// Generate the request results
int[] requestResults = new int[permissions.length];
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
try {
plugin.onRequestPermissionResult(requestCode, permissions, requestResults);
} catch (JSONException e) {
LOG.e(LOG_TAG, "JSONException when delivering permissions results", e);
}
}
}

View File

@ -0,0 +1,70 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.CordovaPlugin;
/**
* This class represents a service entry object.
*/
public final class PluginEntry {
/**
* The name of the service that this plugin implements
*/
public final String service;
/**
* The plugin class name that implements the service.
*/
public final String pluginClass;
/**
* The pre-instantiated plugin to use for this entry.
*/
public final CordovaPlugin plugin;
/**
* Flag that indicates the plugin object should be created when PluginManager is initialized.
*/
public final boolean onload;
/**
* Constructs with a CordovaPlugin already instantiated.
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this(service, plugin.getClass().getName(), true, plugin);
}
/**
* @param service The name of the service
* @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded
*/
public PluginEntry(String service, String pluginClass, boolean onload) {
this(service, pluginClass, onload, null);
}
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
this.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
this.plugin = plugin;
}
}

View File

@ -0,0 +1,526 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.Collection;
import java.util.LinkedHashMap;
import org.json.JSONException;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
*
* Calling native plugin code can be done by calling PluginManager.exec(...)
* from JavaScript.
*/
public class PluginManager {
private static String TAG = "PluginManager";
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
// List of service entries
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>();
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>();
private final CordovaInterface ctx;
private final CordovaWebView app;
private boolean isInitialized;
private CordovaPlugin permissionRequester;
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
this.ctx = cordova;
this.app = cordovaWebView;
setPluginEntries(pluginEntries);
}
public Collection<PluginEntry> getPluginEntries() {
return entryMap.values();
}
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
if (isInitialized) {
this.onPause(false);
this.onDestroy();
pluginMap.clear();
entryMap.clear();
}
for (PluginEntry entry : pluginEntries) {
addService(entry);
}
if (isInitialized) {
startupPlugins();
}
}
/**
* Init when loading a new HTML page into webview.
*/
public void init() {
LOG.d(TAG, "init()");
isInitialized = true;
this.onPause(false);
this.onDestroy();
pluginMap.clear();
this.startupPlugins();
}
/**
* Create plugins objects that have onload set.
*/
private void startupPlugins() {
for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
// When iterating plugins.
if (entry.onload) {
getPlugin(entry.service);
} else {
pluginMap.put(entry.service, null);
}
}
}
/**
* Receives a request for execution and fulfills it by finding the appropriate
* Java class and calling it's execute method.
*
* PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
* string is returned that will indicate if any errors have occurred when trying to find
* or execute the class denoted by the clazz argument.
*
* @param service String containing the service to run
* @param action String containing the action that the class is supposed to perform. This is
* passed to the plugin execute method and it is up to the plugin developer
* how to deal with it.
* @param callbackId String containing the id of the callback that is execute in JavaScript if
* this is an async plugin call.
* @param rawArgs An Array literal string containing any arguments needed in the
* plugin execute method.
*/
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
CordovaPlugin plugin = getPlugin(service);
if (plugin == null) {
LOG.d(TAG, "exec() call to unknown plugin: " + service);
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
app.sendPluginResult(cr, callbackId);
return;
}
CallbackContext callbackContext = new CallbackContext(callbackId, app);
try {
long pluginStartTime = System.currentTimeMillis();
boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
long duration = System.currentTimeMillis() - pluginStartTime;
if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
}
if (!wasValidAction) {
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
callbackContext.sendPluginResult(cr);
}
} catch (JSONException e) {
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(cr);
} catch (Exception e) {
LOG.e(TAG, "Uncaught exception from plugin", e);
callbackContext.error(e.getMessage());
}
}
/**
* Get the plugin object that implements the service.
* If the plugin object does not already exist, then create it.
* If the service doesn't exist, then return null.
*
* @param service The name of the service.
* @return CordovaPlugin or null
*/
public CordovaPlugin getPlugin(String service) {
CordovaPlugin ret = pluginMap.get(service);
if (ret == null) {
PluginEntry pe = entryMap.get(service);
if (pe == null) {
return null;
}
if (pe.plugin != null) {
ret = pe.plugin;
} else {
ret = instantiatePlugin(pe.pluginClass);
}
ret.privateInitialize(service, ctx, app, app.getPreferences());
pluginMap.put(service, ret);
}
return ret;
}
/**
* Add a plugin class that implements a service to the service entry table.
* This does not create the plugin object instance.
*
* @param service The service name
* @param className The plugin class name
*/
public void addService(String service, String className) {
PluginEntry entry = new PluginEntry(service, className, false);
this.addService(entry);
}
/**
* Add a plugin class that implements a service to the service entry table.
* This does not create the plugin object instance.
*
* @param entry The plugin entry
*/
public void addService(PluginEntry entry) {
this.entryMap.put(entry.service, entry);
if (entry.plugin != null) {
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
pluginMap.put(entry.service, entry.plugin);
}
}
/**
* Called when the system is about to start resuming a previous activity.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onPause(multitasking);
}
}
}
/**
* Called when the system received an HTTP authentication request. Plugins can use
* the supplied HttpAuthHandler to process this auth challenge.
*
* @param view The WebView that is initiating the callback
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
*
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
return true;
}
}
return false;
}
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
*
* @param view The WebView that is initiating the callback
* @param request The client certificate request
*
* @return Returns True if plugin will resolve this auth challenge, otherwise False
*
*/
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
return true;
}
}
return false;
}
/**
* Called when the activity will start interacting with the user.
*
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onResume(multitasking);
}
}
}
/**
* Called when the activity is becoming visible to the user.
*/
public void onStart() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStart();
}
}
}
/**
* Called when the activity is no longer visible to the user.
*/
public void onStop() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onStop();
}
}
}
/**
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onDestroy();
}
}
}
/**
* Send a message to all plugins.
*
* @param id The message id
* @param data The message data
* @return Object to stop propagation or null
*/
public Object postMessage(String id, Object data) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Object obj = plugin.onMessage(id, data);
if (obj != null) {
return obj;
}
}
}
return ctx.onMessage(id, data);
}
/**
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onNewIntent(intent);
}
}
}
/**
* Called when the webview is going to request an external resource.
*
* This delegates to the installed plugins, and returns true/false for the
* first plugin to provide a non-null result. If no plugins respond, then
* the default policy is applied.
*
* @param url The URL that is being requested.
* @return Returns true to allow the resource to load,
* false to block the resource.
*/
public boolean shouldAllowRequest(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowRequest(url);
if (result != null) {
return result;
}
}
}
// Default policy:
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
return true;
}
// TalkBack requires this, so allow it by default.
if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {
return true;
}
if (url.startsWith("file://")) {
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return !url.contains("/app_webview/");
}
return false;
}
/**
* Called when the webview is going to change the URL of the loaded content.
*
* This delegates to the installed plugins, and returns true/false for the
* first plugin to provide a non-null result. If no plugins respond, then
* the default policy is applied.
*
* @param url The URL that is being requested.
* @return Returns true to allow the navigation,
* false to block the navigation.
*/
public boolean shouldAllowNavigation(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowNavigation(url);
if (result != null) {
return result;
}
}
}
// Default policy:
return url.startsWith("file://") || url.startsWith("about:blank");
}
/**
* Called when the webview is requesting the exec() bridge be enabled.
*/
public boolean shouldAllowBridgeAccess(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldAllowBridgeAccess(url);
if (result != null) {
return result;
}
}
}
// Default policy:
return url.startsWith("file://");
}
/**
* Called when the webview is going not going to navigate, but may launch
* an Intent for an URL.
*
* This delegates to the installed plugins, and returns true/false for the
* first plugin to provide a non-null result. If no plugins respond, then
* the default policy is applied.
*
* @param url The URL that is being requested.
* @return Returns true to allow the URL to launch an intent,
* false to block the intent.
*/
public Boolean shouldOpenExternalUrl(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null) {
Boolean result = plugin.shouldOpenExternalUrl(url);
if (result != null) {
return result;
}
}
}
// Default policy:
// External URLs are not allowed
return false;
}
/**
* Called when the URL of the webview changes.
*
* @param url The URL that is being changed to.
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
*/
public boolean onOverrideUrlLoading(String url) {
for (PluginEntry entry : this.entryMap.values()) {
CordovaPlugin plugin = pluginMap.get(entry.service);
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
return true;
}
}
return false;
}
/**
* Called when the app navigates or refreshes.
*/
public void onReset() {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onReset();
}
}
}
Uri remapUri(Uri uri) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Uri ret = plugin.remapUri(uri);
if (ret != null) {
return ret;
}
}
}
return null;
}
/**
* Create a plugin based on class name.
*/
private CordovaPlugin instantiatePlugin(String className) {
CordovaPlugin ret = null;
try {
Class<?> c = null;
if ((className != null) && !("".equals(className))) {
c = Class.forName(className);
}
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
ret = (CordovaPlugin) c.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin " + className + ".");
}
return ret;
}
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param newConfig The new device configuration
*/
public void onConfigurationChanged(Configuration newConfig) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
plugin.onConfigurationChanged(newConfig);
}
}
}
public Bundle onSaveInstanceState() {
Bundle state = new Bundle();
for (CordovaPlugin plugin : this.pluginMap.values()) {
if (plugin != null) {
Bundle pluginState = plugin.onSaveInstanceState();
if(pluginState != null) {
state.putBundle(plugin.getServiceName(), pluginState);
}
}
}
return state;
}
}

View File

@ -0,0 +1,198 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Base64;
public class PluginResult {
private final int status;
private final int messageType;
private boolean keepCallback = false;
private String strMessage;
private String encodedMessage;
private List<PluginResult> multipartMessages;
public PluginResult(Status status) {
this(status, PluginResult.StatusMessages[status.ordinal()]);
}
public PluginResult(Status status, String message) {
this.status = status.ordinal();
this.messageType = message == null ? MESSAGE_TYPE_NULL : MESSAGE_TYPE_STRING;
this.strMessage = message;
}
public PluginResult(Status status, JSONArray message) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_JSON;
encodedMessage = message.toString();
}
public PluginResult(Status status, JSONObject message) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_JSON;
encodedMessage = message.toString();
}
public PluginResult(Status status, int i) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_NUMBER;
this.encodedMessage = ""+i;
}
public PluginResult(Status status, float f) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_NUMBER;
this.encodedMessage = ""+f;
}
public PluginResult(Status status, boolean b) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_BOOLEAN;
this.encodedMessage = Boolean.toString(b);
}
public PluginResult(Status status, byte[] data) {
this(status, data, false);
}
public PluginResult(Status status, byte[] data, boolean binaryString) {
this.status = status.ordinal();
this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
}
// The keepCallback and status of multipartMessages are ignored.
public PluginResult(Status status, List<PluginResult> multipartMessages) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_MULTIPART;
this.multipartMessages = multipartMessages;
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
public int getStatus() {
return status;
}
public int getMessageType() {
return messageType;
}
public String getMessage() {
if (encodedMessage == null) {
encodedMessage = JSONObject.quote(strMessage);
}
return encodedMessage;
}
public int getMultipartMessagesSize() {
return multipartMessages.size();
}
public PluginResult getMultipartMessage(int index) {
return multipartMessages.get(index);
}
/**
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
* Otherwise, returns null.
*/
public String getStrMessage() {
return strMessage;
}
public boolean getKeepCallback() {
return this.keepCallback;
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String getJSONString() {
return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}";
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toCallbackString(String callbackId) {
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
return null;
}
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
return toSuccessCallbackString(callbackId);
}
return toErrorCallbackString(callbackId);
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toSuccessCallbackString(String callbackId) {
return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
}
@Deprecated // Use sendPluginResult instead of sendJavascript.
public String toErrorCallbackString(String callbackId) {
return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
}
public static final int MESSAGE_TYPE_STRING = 1;
public static final int MESSAGE_TYPE_JSON = 2;
public static final int MESSAGE_TYPE_NUMBER = 3;
public static final int MESSAGE_TYPE_BOOLEAN = 4;
public static final int MESSAGE_TYPE_NULL = 5;
public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
// Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static final int MESSAGE_TYPE_MULTIPART = 8;
public static String[] StatusMessages = new String[] {
"No result",
"OK",
"Class not found",
"Illegal access",
"Instantiation error",
"Malformed url",
"IO error",
"Invalid action",
"JSON error",
"Error"
};
public enum Status {
NO_RESULT,
OK,
CLASS_NOT_FOUND_EXCEPTION,
ILLEGAL_ACCESS_EXCEPTION,
INSTANTIATION_EXCEPTION,
MALFORMED_URL_EXCEPTION,
IO_EXCEPTION,
INVALID_ACTION,
JSON_EXCEPTION,
ERROR
}
}

View File

@ -0,0 +1,76 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class ResumeCallback extends CallbackContext {
private final String TAG = "CordovaResumeCallback";
private String serviceName;
private PluginManager pluginManager;
public ResumeCallback(String serviceName, PluginManager pluginManager) {
super("resumecallback", null);
this.serviceName = serviceName;
this.pluginManager = pluginManager;
}
@Override
public void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (finished) {
LOG.w(TAG, serviceName + " attempted to send a second callback to ResumeCallback\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = true;
}
}
JSONObject event = new JSONObject();
JSONObject pluginResultObject = new JSONObject();
try {
pluginResultObject.put("pluginServiceName", this.serviceName);
pluginResultObject.put("pluginStatus", PluginResult.StatusMessages[pluginResult.getStatus()]);
event.put("action", "resume");
event.put("pendingResult", pluginResultObject);
} catch (JSONException e) {
LOG.e(TAG, "Unable to create resume object for Activity Result");
}
PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event);
// We send a list of results to the js so that we don't have to decode
// the PluginResult passed to this CallbackContext into JSON twice.
// The results are combined into an event payload before the event is
// fired on the js side of things (see platform.js)
List<PluginResult> result = new ArrayList<PluginResult>();
result.add(eventResult);
result.add(pluginResult);
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, result));
}
}

View File

@ -0,0 +1,170 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.LOG;
import android.net.Uri;
public class Whitelist {
private static class URLPattern {
public Pattern scheme;
public Pattern host;
public Integer port;
public Pattern path;
private String regexFromPattern(String pattern, boolean allowWildcards) {
final String toReplace = "\\.[]{}()^$?+|";
StringBuilder regex = new StringBuilder();
for (int i=0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '*' && allowWildcards) {
regex.append(".");
} else if (toReplace.indexOf(c) > -1) {
regex.append('\\');
}
regex.append(c);
}
return regex.toString();
}
public URLPattern(String scheme, String host, String port, String path) throws MalformedURLException {
try {
if (scheme == null || "*".equals(scheme)) {
this.scheme = null;
} else {
this.scheme = Pattern.compile(regexFromPattern(scheme, false), Pattern.CASE_INSENSITIVE);
}
if ("*".equals(host)) {
this.host = null;
} else if (host.startsWith("*.")) {
this.host = Pattern.compile("([a-z0-9.-]*\\.)?" + regexFromPattern(host.substring(2), false), Pattern.CASE_INSENSITIVE);
} else {
this.host = Pattern.compile(regexFromPattern(host, false), Pattern.CASE_INSENSITIVE);
}
if (port == null || "*".equals(port)) {
this.port = null;
} else {
this.port = Integer.parseInt(port,10);
}
if (path == null || "/*".equals(path)) {
this.path = null;
} else {
this.path = Pattern.compile(regexFromPattern(path, true));
}
} catch (NumberFormatException e) {
throw new MalformedURLException("Port must be a number");
}
}
public boolean matches(Uri uri) {
try {
return ((scheme == null || scheme.matcher(uri.getScheme()).matches()) &&
(host == null || host.matcher(uri.getHost()).matches()) &&
(port == null || port.equals(uri.getPort())) &&
(path == null || path.matcher(uri.getPath()).matches()));
} catch (Exception e) {
LOG.d(TAG, e.toString());
return false;
}
}
}
private ArrayList<URLPattern> whiteList;
public static final String TAG = "Whitelist";
public Whitelist() {
this.whiteList = new ArrayList<URLPattern>();
}
/* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
*
* <url-pattern> := <scheme>://<host><path>
* <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension'
* <host> := '*' | '*.' <any char except '/' and '*'>+
* <path> := '/' <any chars>
*
* We extend this to explicitly allow a port attached to the host, and we allow
* the scheme to be omitted for backwards compatibility. (Also host is not required
* to begin with a "*" or "*.".)
*/
public void addWhiteListEntry(String origin, boolean subdomains) {
if (whiteList != null) {
try {
// Unlimited access to network resources
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
whiteList = null;
}
else { // specific access
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
Matcher m = parts.matcher(origin);
if (m.matches()) {
String scheme = m.group(2);
String host = m.group(4);
// Special case for two urls which are allowed to have empty hosts
if (("file".equals(scheme) || "content".equals(scheme)) && host == null) host = "*";
String port = m.group(8);
String path = m.group(9);
if (scheme == null) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
whiteList.add(new URLPattern("http", host, port, path));
whiteList.add(new URLPattern("https", host, port, path));
} else {
whiteList.add(new URLPattern(scheme, host, port, path));
}
}
}
} catch (Exception e) {
LOG.d(TAG, "Failed to add origin %s", origin);
}
}
}
/**
* Determine if URL is in approved list of URLs to load.
*
* @param uri
* @return true if wide open or whitelisted
*/
public boolean isUrlWhiteListed(String uri) {
// If there is no whitelist, then it's wide open
if (whiteList == null) return true;
Uri parsedUri = Uri.parse(uri);
// Look for match in white list
Iterator<URLPattern> pit = whiteList.iterator();
while (pit.hasNext()) {
URLPattern p = pit.next();
if (p.matches(parsedUri)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,69 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.CookieManager;
import android.webkit.WebView;
import org.apache.cordova.ICordovaCookieManager;
class SystemCookieManager implements ICordovaCookieManager {
protected final WebView webView;
private final CookieManager cookieManager;
//Added because lint can't see the conditional RIGHT ABOVE this
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SystemCookieManager(WebView webview) {
webView = webview;
cookieManager = CookieManager.getInstance();
//REALLY? Nobody has seen this UNTIL NOW?
cookieManager.setAcceptFileSchemeCookies(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, true);
}
}
public void setCookiesEnabled(boolean accept) {
cookieManager.setAcceptCookie(accept);
}
public void setCookie(final String url, final String value) {
cookieManager.setCookie(url, value);
}
public String getCookie(final String url) {
return cookieManager.getCookie(url);
}
public void clearCookies() {
cookieManager.removeAllCookie();
}
public void flush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
}
}
};

View File

@ -0,0 +1,53 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import android.webkit.JavascriptInterface;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ExposedJsApi;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
class SystemExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
SystemExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}

View File

@ -0,0 +1,292 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import java.util.Arrays;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.PermissionRequest;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import org.apache.cordova.CordovaDialogsHelper;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
* The kind of callbacks that happen here are on the chrome outside the document,
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
* to but different than CordovaWebViewClient.
*/
public class SystemWebChromeClient extends WebChromeClient {
private static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "SystemWebChromeClient";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected final SystemWebViewEngine parentEngine;
// the video progress view
private View mVideoProgressView;
private CordovaDialogsHelper dialogsHelper;
private Context appContext;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private View mCustomView;
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
appContext = parentEngine.webView.getContext();
dialogsHelper = new CordovaDialogsHelper(appContext);
}
/**
* Tell the client to display a javascript alert dialog.
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
@Override public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*/
@Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirm(handledRet);
} else {
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm(value);
} else {
result.cancel();
}
}
});
}
return true;
}
/**
* Handle database quota exceeded notification.
*/
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
{
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
quotaUpdater.updateQuota(MAX_QUOTA);
}
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
// Expect this to not compile in a future Android release!
@SuppressWarnings("deprecation")
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID)
{
//This is only for Android 2.1
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
{
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@TargetApi(8)
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@Override
/**
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
*
* This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
*
* @param origin
* @param callback
*/
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
//Get the plugin, it should be loaded
CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
if(geolocation != null && !geolocation.hasPermisssion())
{
geolocation.requestPermissions(0);
}
}
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
parentEngine.getCordovaWebView().showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
parentEngine.getCordovaWebView().hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
// <input type=file> support:
// openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat).
// For Lollipop, we use onShowFileChooser().
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
uploadMsg.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
Intent intent = fileChooserParams.createIntent();
try {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
LOG.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onPermissionRequest(final PermissionRequest request) {
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
request.grant(request.getResources());
}
public void destroyLastDialog(){
dialogsHelper.destroyLastDialog();
}
}

View File

@ -0,0 +1,88 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
/**
* Custom WebView subclass that enables us to capture events needed for Cordova.
*/
public class SystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
private SystemWebViewClient viewClient;
SystemWebChromeClient chromeClient;
private SystemWebViewEngine parentEngine;
private CordovaInterface cordova;
public SystemWebView(Context context) {
this(context, null);
}
public SystemWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Package visibility to enforce that only SystemWebViewEngine should call this method.
void init(SystemWebViewEngine parentEngine, CordovaInterface cordova) {
this.cordova = cordova;
this.parentEngine = parentEngine;
if (this.viewClient == null) {
setWebViewClient(new SystemWebViewClient(parentEngine));
}
if (this.chromeClient == null) {
setWebChromeClient(new SystemWebChromeClient(parentEngine));
}
}
@Override
public CordovaWebView getCordovaWebView() {
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
}
@Override
public void setWebViewClient(WebViewClient client) {
viewClient = (SystemWebViewClient)client;
super.setWebViewClient(client);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
chromeClient = (SystemWebChromeClient)client;
super.setWebChromeClient(client);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
if (ret != null) {
return ret.booleanValue();
}
return super.dispatchKeyEvent(event);
}
}

View File

@ -0,0 +1,374 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import android.annotation.TargetApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.apache.cordova.AuthenticationToken;
import org.apache.cordova.CordovaClientCertRequest;
import org.apache.cordova.CordovaHttpAuthHandler;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
/**
* This class is the WebViewClient that implements callbacks for our web view.
* The kind of callbacks that happen here are regarding the rendering of the
* document instead of the chrome surrounding it, such as onPageStarted(),
* shouldOverrideUrlLoading(), etc. Related to but different than
* CordovaChromeClient.
*/
public class SystemWebViewClient extends WebViewClient {
private static final String TAG = "SystemWebViewClient";
protected final SystemWebViewEngine parentEngine;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
/** The authorization tokens. */
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return parentEngine.client.onNavigationAttempt(url);
}
/**
* On received http auth request.
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// Get the authentication token (if specified)
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
return;
}
// Check if there is some plugin which can resolve this auth challenge
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
/**
* On received client cert request.
* The method forwards the request to any running plugins before using the default implementation.
*
* @param view
* @param request
*/
@Override
@TargetApi(21)
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
{
// Check if there is some plugin which can resolve this certificate request
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
// By default pass to WebViewClient
super.onReceivedClientCertRequest(view, request);
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isCurrentlyLoading = true;
// Flush stale messages & reset plugins.
parentEngine.bridge.reset();
parentEngine.client.onPageStarted(url);
}
/**
* Notify the host application that a page has finished loading.
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
*
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Ignore excessive calls, if url is not about:blank (CB-8317).
if (!isCurrentlyLoading && !url.startsWith("about:")) {
return;
}
isCurrentlyLoading = false;
/**
* Because of a timing issue we need to clear this history in onPageFinished as well as
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
* true. You see when you load a url with a # in it which is common in jQuery applications
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
*/
if (this.doClearHistory) {
view.clearHistory();
this.doClearHistory = false;
}
parentEngine.client.onPageFinishedLoading(url);
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// Ignore error due to stopLoading().
if (!isCurrentlyLoading) {
return;
}
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
// If this is a "Protocol Not Supported" error, then revert to the previous
// page. If there was no previous page, then punt. The application's config
// is likely incorrect (start page set to sms: or something like that)
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
parentEngine.client.clearLoadTimeoutTimer();
if (view.canGoBack()) {
view.goBack();
return;
} else {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
}
/**
* Notify the host application that an SSL error occurred while loading a resource.
* The host application must call either handler.cancel() or handler.proceed().
* Note that the decision may be retained for use in response to future SSL errors.
* The default behavior is to cancel the load.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's response.
* @param error The SSL error object.
*/
@TargetApi(8)
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final String packageName = parentEngine.cordova.getActivity().getPackageName();
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
handler.proceed();
return;
} else {
// debug = false
super.onReceivedSslError(view, handler, error);
}
} catch (NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(view, handler, error);
}
}
/**
* Sets the authentication token.
*
* @param authenticationToken
* @param host
* @param realm
*/
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
if (host == null) {
host = "";
}
if (realm == null) {
realm = "";
}
this.authenticationTokens.put(host.concat(realm), authenticationToken);
}
/**
* Removes the authentication token.
*
* @param host
* @param realm
*
* @return the authentication token or null if did not exist
*/
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
return this.authenticationTokens.remove(host.concat(realm));
}
/**
* Gets the authentication token.
*
* In order it tries:
* 1- host + realm
* 2- host
* 3- realm
* 4- no host, no realm
*
* @param host
* @param realm
*
* @return the authentication token
*/
public AuthenticationToken getAuthenticationToken(String host, String realm) {
AuthenticationToken token = null;
token = this.authenticationTokens.get(host.concat(realm));
if (token == null) {
// try with just the host
token = this.authenticationTokens.get(host);
// Try the realm
if (token == null) {
token = this.authenticationTokens.get(realm);
}
// if no host found, just query for default
if (token == null) {
token = this.authenticationTokens.get("");
}
}
return token;
}
/**
* Clear all authentication tokens.
*/
public void clearAuthenticationTokens() {
this.authenticationTokens.clear();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Changing this will cause problems for your application
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
CordovaResourceApi resourceApi = parentEngine.resourceApi;
Uri origUri = Uri.parse(url);
// Allow plugins to intercept WebView requests.
Uri remappedUri = resourceApi.remapUri(origUri);
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
}
// If we don't need to special-case the request, let the browser load it.
return null;
} catch (IOException e) {
if (!(e instanceof FileNotFoundException)) {
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
}
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
}
private static boolean needsKitKatContentUrlFix(Uri uri) {
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
}
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
return false;
}
if (uri.getQuery() != null || uri.getFragment() != null) {
return true;
}
if (!uri.toString().contains("%")) {
return false;
}
switch(android.os.Build.VERSION.SDK_INT){
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
return true;
}
return false;
}
}

View File

@ -0,0 +1,357 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.engine;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPreferences;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.ICordovaCookieManager;
import org.apache.cordova.LOG;
import org.apache.cordova.NativeToJsMessageQueue;
import org.apache.cordova.PluginManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
* We make the Engine separate from the actual View so that:
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
* B) Separating the actual View from the Engine makes API surfaces smaller.
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
*/
public class SystemWebViewEngine implements CordovaWebViewEngine {
public static final String TAG = "SystemWebViewEngine";
protected final SystemWebView webView;
protected final SystemCookieManager cookieManager;
protected CordovaPreferences preferences;
protected CordovaBridge bridge;
protected CordovaWebViewEngine.Client client;
protected CordovaWebView parentWebView;
protected CordovaInterface cordova;
protected PluginManager pluginManager;
protected CordovaResourceApi resourceApi;
protected NativeToJsMessageQueue nativeToJsMessageQueue;
private BroadcastReceiver receiver;
/** Used when created via reflection. */
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
this(new SystemWebView(context), preferences);
}
public SystemWebViewEngine(SystemWebView webView) {
this(webView, null);
}
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
this.preferences = preferences;
this.webView = webView;
cookieManager = new SystemCookieManager(webView);
}
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
if (this.cordova != null) {
throw new IllegalStateException();
}
// Needed when prefs are not passed by the constructor
if (preferences == null) {
preferences = parentWebView.getPreferences();
}
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
this.resourceApi = resourceApi;
this.pluginManager = pluginManager;
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
webView.init(this, cordova);
initWebViewSettings();
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override
public void setNetworkAvailable(boolean value) {
//sometimes this can be called after calling webview.destroy() on destroy()
//thus resulting in a NullPointerException
if(webView!=null) {
webView.setNetworkAvailable(value);
}
}
@Override
public void runOnUiThread(Runnable r) {
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
exposeJsInterface(webView, bridge);
}
@Override
public CordovaWebView getCordovaWebView() {
return parentWebView;
}
@Override
public ICordovaCookieManager getCookieManager() {
return cookieManager;
}
@Override
public View getView() {
return webView;
}
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
webView.setInitialScale(0);
webView.setVerticalScrollBarEnabled(false);
// Enable JavaScript
final WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
String manufacturer = android.os.Build.MANUFACTURER;
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
android.os.Build.MANUFACTURER.contains("HTC"))
{
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
LOG.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
LOG.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
LOG.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
LOG.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
settings.setMediaPlaybackRequiresUserGesture(false);
}
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
enableRemoteDebugging();
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
String defaultUserAgent = settings.getUserAgentString();
// Fix for CB-3360
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
if (overrideUserAgent != null) {
settings.setUserAgentString(overrideUserAgent);
} else {
String appendUserAgent = preferences.getString("AppendUserAgent", null);
if (appendUserAgent != null) {
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
}
}
// End CB-3360
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
settings.getUserAgentString();
}
};
webView.getContext().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void enableRemoteDebugging() {
try {
WebView.setWebContentsDebuggingEnabled(true);
} catch (IllegalArgumentException e) {
LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
}
}
// Yeah, we know, which is why we makes ure that we don't do this if the bridge is
// below JELLYBEAN_MR1. It'd be great if lint was just a little smarter.
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
LOG.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
}
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
/**
* Load the url into the webview.
*/
@Override
public void loadUrl(final String url, boolean clearNavigationStack) {
webView.loadUrl(url);
}
@Override
public String getUrl() {
return webView.getUrl();
}
@Override
public void stopLoading() {
webView.stopLoading();
}
@Override
public void clearCache() {
webView.clearCache(true);
}
@Override
public void clearHistory() {
webView.clearHistory();
}
@Override
public boolean canGoBack() {
return webView.canGoBack();
}
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
@Override
public boolean goBack() {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
if (webView.canGoBack()) {
webView.goBack();
return true;
}
return false;
}
@Override
public void setPaused(boolean value) {
if (value) {
webView.onPause();
webView.pauseTimers();
} else {
webView.onResume();
webView.resumeTimers();
}
}
@Override
public void destroy() {
webView.chromeClient.destroyLastDialog();
webView.destroy();
// unregister the receiver
if (receiver != null) {
try {
webView.getContext().unregisterReceiver(receiver);
} catch (Exception e) {
LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
@Override
public void evaluateJavascript(String js, ValueCallback<String> callback) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(js, callback);
}
else
{
LOG.d(TAG, "This webview is using the old bridge");
}
}
}

View File

@ -0,0 +1,184 @@
{
"prepare_queue": {
"installed": [],
"uninstalled": []
},
"config_munge": {
"files": {
"*-Info.plist": {
"parents": {
"NSCameraUsageDescription": [
{
"xml": "<string>need camera access to take pictures</string>",
"count": 1,
"mode": "merge",
"id": "config.xml"
}
]
}
},
"res/xml/config.xml": {
"parents": {
"/*": [
{
"xml": "<feature name=\"Camera\"><param name=\"android-package\" value=\"org.apache.cordova.camera.CameraLauncher\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Notification\"><param name=\"android-package\" value=\"org.apache.cordova.dialogs.Notification\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Whitelist\"><param name=\"android-package\" value=\"org.apache.cordova.whitelist.WhitelistPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"BarcodeScanner\"><param name=\"android-package\" value=\"com.phonegap.plugins.barcodescanner.BarcodeScanner\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"LocalNotifications\"><param name=\"android-package\" value=\"com.adobe.phonegap.notification.LocalNotifications\" /></feature>",
"count": 1
}
]
}
},
"AndroidManifest.xml": {
"parents": {
"/*": [
{
"xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
"count": 1
}
],
"application": [
{
"xml": "<provider android:authorities=\"${applicationId}.provider\" android:exported=\"false\" android:grantUriPermissions=\"true\" android:name=\"org.apache.cordova.camera.FileProvider\"><meta-data android:name=\"android.support.FILE_PROVIDER_PATHS\" android:resource=\"@xml/camera_provider_paths\" /></provider>",
"count": 1
}
],
"/manifest/application": [
{
"xml": "<activity android:clearTaskOnLaunch=\"true\" android:configChanges=\"orientation|keyboardHidden|screenSize\" android:exported=\"false\" android:name=\"com.google.zxing.client.android.CaptureActivity\" android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\" android:windowSoftInputMode=\"stateAlwaysHidden\" />",
"count": 1
},
{
"xml": "<activity android:label=\"Share\" android:name=\"com.google.zxing.client.android.encode.EncodeActivity\" />",
"count": 1
},
{
"xml": "<activity android:exported=\"true\" android:name=\"com.adobe.phonegap.notification.NotificationHandlerActivity\" android:permission=\"${applicationId}.permission.PushHandlerActivity\" />",
"count": 1
}
],
"/manifest": [
{
"xml": "<uses-permission android:name=\"android.permission.CAMERA\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.FLASHLIGHT\" />",
"count": 1
},
{
"xml": "<uses-feature android:name=\"android.hardware.camera\" android:required=\"true\" />",
"count": 1
}
]
}
}
}
},
"installed_plugins": {
"cordova-plugin-camera": {
"PACKAGE_NAME": "io.nicco.app.fotm.cordova"
},
"cordova-plugin-dialogs": {
"PACKAGE_NAME": "io.nicco.app.fotm.cordova"
},
"cordova-plugin-whitelist": {
"PACKAGE_NAME": "io.nicco.app.fotm.cordova"
},
"phonegap-plugin-barcodescanner": {
"ANDROID_SUPPORT_V4_VERSION": "27.+",
"PACKAGE_NAME": "io.nicco.app.fotm.cordova"
},
"phonegap-plugin-local-notification": {
"PACKAGE_NAME": "io.nicco.app.fotm.cordova"
}
},
"dependent_plugins": {},
"modules": [
{
"id": "cordova-plugin-camera.Camera",
"file": "plugins/cordova-plugin-camera/www/CameraConstants.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"Camera"
]
},
{
"id": "cordova-plugin-camera.CameraPopoverOptions",
"file": "plugins/cordova-plugin-camera/www/CameraPopoverOptions.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"CameraPopoverOptions"
]
},
{
"id": "cordova-plugin-camera.camera",
"file": "plugins/cordova-plugin-camera/www/Camera.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"navigator.camera"
]
},
{
"id": "cordova-plugin-camera.CameraPopoverHandle",
"file": "plugins/cordova-plugin-camera/www/CameraPopoverHandle.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"CameraPopoverHandle"
]
},
{
"id": "cordova-plugin-dialogs.notification",
"file": "plugins/cordova-plugin-dialogs/www/notification.js",
"pluginId": "cordova-plugin-dialogs",
"merges": [
"navigator.notification"
]
},
{
"id": "cordova-plugin-dialogs.notification_android",
"file": "plugins/cordova-plugin-dialogs/www/android/notification.js",
"pluginId": "cordova-plugin-dialogs",
"merges": [
"navigator.notification"
]
},
{
"id": "phonegap-plugin-barcodescanner.BarcodeScanner",
"file": "plugins/phonegap-plugin-barcodescanner/www/barcodescanner.js",
"pluginId": "phonegap-plugin-barcodescanner",
"clobbers": [
"cordova.plugins.barcodeScanner"
]
},
{
"id": "phonegap-plugin-local-notification.Notification",
"file": "plugins/phonegap-plugin-local-notification/www/notification.js",
"pluginId": "phonegap-plugin-local-notification",
"clobbers": [
"Notification"
]
}
],
"plugin_metadata": {
"cordova-plugin-camera": "4.0.3",
"cordova-plugin-dialogs": "2.0.1",
"cordova-plugin-whitelist": "1.3.3",
"phonegap-plugin-barcodescanner": "8.0.0",
"phonegap-plugin-local-notification": "1.0.1"
}
}

View File

@ -0,0 +1,327 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral()
jcenter()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
allprojects {
repositories {
mavenCentral();
jcenter()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.1.0'
}
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
ext {
apply from: '../CordovaLib/cordova.gradle'
// The value for android.compileSdkVersion.
if (!project.hasProperty('cdvCompileSdkVersion')) {
cdvCompileSdkVersion = null;
}
// The value for android.buildToolsVersion.
if (!project.hasProperty('cdvBuildToolsVersion')) {
cdvBuildToolsVersion = null;
}
// Sets the versionCode to the given value.
if (!project.hasProperty('cdvVersionCode')) {
cdvVersionCode = null
}
// Sets the minSdkVersion to the given value.
if (!project.hasProperty('cdvMinSdkVersion')) {
cdvMinSdkVersion = null
}
// Whether to build architecture-specific APKs.
if (!project.hasProperty('cdvBuildMultipleApks')) {
cdvBuildMultipleApks = null
}
// Whether to append a 0 "abi digit" to versionCode when only a single APK is build
if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
cdvVersionCodeForceAbiDigit = null
}
// .properties files to use for release signing.
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
cdvReleaseSigningPropertiesFile = null
}
// .properties files to use for debug signing.
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
cdvDebugSigningPropertiesFile = null
}
// Set by build.js script.
if (!project.hasProperty('cdvBuildArch')) {
cdvBuildArch = null
}
// Plugin gradle extensions can append to this to have code run at the end.
cdvPluginPostBuildExtras = []
}
// PLUGIN GRADLE EXTENSIONS START
apply from: "../phonegap-plugin-barcodescanner/cordova-barcodescanner.gradle"
// PLUGIN GRADLE EXTENSIONS END
def hasBuildExtras = file('build-extras.gradle').exists()
if (hasBuildExtras) {
apply from: 'build-extras.gradle'
}
// Set property defaults after extension .gradle files.
if (ext.cdvCompileSdkVersion == null) {
ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
//ext.cdvCompileSdkVersion = project.ext.defaultCompileSdkVersion
}
if (ext.cdvBuildToolsVersion == null) {
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
//ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
}
if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) {
ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties'
}
if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.properties').exists()) {
ext.cdvReleaseSigningPropertiesFile = '../release-signing.properties'
}
// Cast to appropriate types.
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : defaultMinSdkVersion
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
def computeBuildTargetName(debugBuild) {
def ret = 'assemble'
if (cdvBuildMultipleApks && cdvBuildArch) {
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
}
return ret + (debugBuild ? 'Debug' : 'Release')
}
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
task cdvBuildDebug
cdvBuildDebug.dependsOn {
return computeBuildTargetName(true)
}
task cdvBuildRelease
cdvBuildRelease.dependsOn {
return computeBuildTargetName(false)
}
task cdvPrintProps << {
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
println('cdvVersionCode=' + cdvVersionCode)
println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
println('cdvBuildArch=' + cdvBuildArch)
println('computedVersionCode=' + android.defaultConfig.versionCode)
android.productFlavors.each { flavor ->
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
}
}
android {
defaultConfig {
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
applicationId privateHelpers.extractStringFromManifest("package")
if (cdvMinSdkVersion != null) {
minSdkVersion cdvMinSdkVersion
}
}
lintOptions {
abortOnError false;
}
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
//This code exists for Crosswalk and other Native APIs.
//By default, we multiply the existing version code in the Android Manifest by 10 and
//add a number for each architecture. If you are not using Crosswalk or SQLite, you can
//ignore this chunk of code, and your version codes will be respected.
if (Boolean.valueOf(cdvBuildMultipleApks)) {
flavorDimensions "default"
productFlavors {
armeabi {
versionCode defaultConfig.versionCode*10 + 1
ndk {
abiFilters = ["armeabi"]
}
}
armv7 {
versionCode defaultConfig.versionCode*10 + 2
ndk {
abiFilters = ["armeabi-v7a"]
}
}
arm64 {
versionCode defaultConfig.versionCode*10 + 3
ndk {
abiFilters = ["arm64-v8a"]
}
}
x86 {
versionCode defaultConfig.versionCode*10 + 4
ndk {
abiFilters = ["x86"]
}
}
x86_64 {
versionCode defaultConfig.versionCode*10 + 5
ndk {
abiFilters = ["x86_64"]
}
}
}
} else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
// This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
defaultConfig {
versionCode defaultConfig.versionCode*10
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
if (cdvReleaseSigningPropertiesFile) {
signingConfigs {
release {
// These must be set or Gradle will complain (even if they are overridden).
keyAlias = ""
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
storeFile = null
storePassword = "__unset"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
}
if (cdvDebugSigningPropertiesFile) {
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
}
}
/*
* WARNING: Cordova Lib and platform scripts do management inside of this code here,
* if you are adding the dependencies manually, do so outside the comments, otherwise
* the Cordova tools will overwrite them
*/
dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
implementation(project(path: ":CordovaLib"))
compile "com.android.support:support-v4:24.1.1+"
compile "com.android.support:support-v4:27.+"
compile "com.android.support:support-v13:23+"
// SUB-PROJECT DEPENDENCIES END
}
def promptForReleaseKeyPassword() {
if (!cdvReleaseSigningPropertiesFile) {
return;
}
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
}
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
}
}
gradle.taskGraph.whenReady { taskGraph ->
taskGraph.getAllTasks().each() { task ->
if(['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
promptForReleaseKeyPassword()
}
}
}
def addSigningProps(propsFilePath, signingConfig) {
def propsFile = file(propsFilePath)
def props = new Properties()
propsFile.withReader { reader ->
props.load(reader)
}
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
if (!storeFile.isAbsolute()) {
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
}
if (!storeFile.exists()) {
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
}
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
signingConfig.storeFile = storeFile
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
def storeType = props.get('storeType', props.get('key.store.type', ''))
if (!storeType) {
def filename = storeFile.getName().toLowerCase();
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
storeType = 'pkcs12'
} else {
storeType = signingConfig.storeType // "jks"
}
}
signingConfig.storeType = storeType
}
for (def func : cdvPluginPostBuildExtras) {
func()
}
// This can be defined within build-extras.gradle as:
// ext.postBuildExtras = { ... code here ... }
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}

View File

@ -0,0 +1,24 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="10000" android:versionName="1.0.0" package="io.nicco.app.fotm.cordova" xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/launcher_name">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true" android:name="org.apache.cordova.camera.FileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/camera_provider_paths" />
</provider>
<activity android:clearTaskOnLaunch="true" android:configChanges="orientation|keyboardHidden|screenSize" android:exported="false" android:name="com.google.zxing.client.android.CaptureActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:windowSoftInputMode="stateAlwaysHidden" />
<activity android:label="Share" android:name="com.google.zxing.client.android.encode.EncodeActivity" />
<activity android:exported="true" android:name="com.adobe.phonegap.notification.NotificationHandlerActivity" android:permission="${applicationId}.permission.PushHandlerActivity" />
</application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,129 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 100vw;
height: 100vh;
overflow: hidden;
}
img {
object-fit: contain;
}
.spinning {
animation: rotation 2s infinite linear;
}
@keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(359deg);
}
}
.fill {
width: 100%;
height: 100%;
}
.column {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
.abs {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.button {
border: 1px solid #000;
height: 45px;
width: 180px;
border-radius: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.bold {
font-weight: bold;
}
#app {
width: 100%;
height: 100%;
font-family: 'Helvetica Neue';
}
#app .bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
background-image: linear-gradient(135deg, #6FABFF, #E4FF71);
}
#app .content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.Logo {
position: relative;
font-family: 'Jaapokki';
}
.Logo .title {
font-size: 30px;
}
.Logo .badge {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: -20px;
top: -5px;
width: 20px;
height: 20px;
background-color: #f00;
border-radius: 20px;
}
.Logo .badge div {
font-size: 10px;
}
@font-face {
font-family: 'Jaapokki';
src: url(assets/Jaapokki.otf);
}
@font-face {
font-family: 'Helvetiva Neue';
font-weight: lighter;
font-style: normal;
src: url("assets/Helvetica Neue LT Std Light.otf") format('opentype');
}
@font-face {
font-family: 'Helvetiva Neue';
font-weight: normal;
font-style: normal;
src: url("assets/Helvetica Neue LT Std Medium.otf") format('opentype');
}
@font-face {
font-family: 'Helvetiva Neue';
font-weight: bold;
font-style: normal;
src: url("assets/Helvetica Neue LT Std Bold.otf") format('opentype');
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Exports the ExposedJsApi.java object if available, otherwise exports the PromptBasedNativeApi.
*/
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
var currentApi = nativeApi;
module.exports = {
get: function() { return currentApi; },
setPreferPrompt: function(value) {
currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
},
// Used only by tests.
set: function(value) {
currentApi = value;
}
};

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
*/
module.exports = {
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
},
setNativeToJsBridgeMode: function(bridgeSecret, value) {
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
},
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
}
};

View File

@ -0,0 +1,297 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Execute a cordova command. It is up to the native side whether this action
* is synchronous or asynchronous. The native side can return:
* Synchronous: PluginResult object as a JSON string
* Asynchronous: Empty string ""
* If async, the native side will cordova.callbackSuccess or cordova.callbackError,
* depending upon the result of the action.
*
* @param {Function} success The success callback
* @param {Function} fail The fail callback
* @param {String} service The name of the service to use
* @param {String} action Action to be run in cordova
* @param {String[]} [args] Zero or more arguments to pass to the method
*/
var cordova = require('cordova'),
nativeApiProvider = require('cordova/android/nativeapiprovider'),
utils = require('cordova/utils'),
base64 = require('cordova/base64'),
channel = require('cordova/channel'),
jsToNativeModes = {
PROMPT: 0,
JS_OBJECT: 1
},
nativeToJsModes = {
// Polls for messages using the JS->Native bridge.
POLLING: 0,
// For LOAD_URL to be viable, it would need to have a work-around for
// the bug where the soft-keyboard gets dismissed when a message is sent.
LOAD_URL: 1,
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
ONLINE_EVENT: 2,
EVAL_BRIDGE: 3
},
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,
bridgeSecret = -1;
var messagesFromNative = [];
var isProcessing = false;
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };
function androidExec(success, fail, service, action, args) {
if (bridgeSecret < 0) {
// If we ever catch this firing, we'll need to queue up exec()s
// and fire them once we get a secret. For now, I don't think
// it's possible for exec() to be called since plugins are parsed but
// not run until until after onNativeReady.
throw new Error('exec() called without bridgeSecret');
}
// Set default bridge modes if they have not already been set.
// By default, we use the failsafe, since addJavascriptInterface breaks too often
if (jsToNativeBridgeMode === undefined) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
// If args is not provided, default to an empty array
args = args || [];
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
args[i] = base64.fromArrayBuffer(args[i]);
}
}
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
androidExec(success, fail, service, action, args);
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
} else if (msgs) {
messagesFromNative.push(msgs);
// Always process async to avoid exceptions messing up stack.
nextTick(processMessages);
}
}
androidExec.init = function() {
//CB-11828
//This failsafe checks the version of Android and if it's Jellybean, it switches it to
//using the Online Event bridge for communicating from Native to JS
//
//It's ugly, but it's necessary.
var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
var version_code = check && check[0].match(/4.[0-3].*/);
if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
}
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
function pollOnceFromOnlineEvent() {
pollOnce(true);
}
function pollOnce(opt_fromOnlineEvent) {
if (bridgeSecret < 0) {
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
// We know there's nothing to retrieve, so no need to poll.
return;
}
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
if (msgs) {
messagesFromNative.push(msgs);
// Process sync since we know we're already top-of-stack.
processMessages();
}
}
function pollingTimerFunc() {
if (pollEnabled) {
pollOnce();
setTimeout(pollingTimerFunc, 50);
}
}
function hookOnlineApis() {
function proxyEvent(e) {
cordova.fireWindowEvent(e.type);
}
// The network module takes care of firing online and offline events.
// It currently fires them only on document though, so we bridge them
// to window here (while first listening for exec()-releated online/offline
// events).
window.addEventListener('online', pollOnceFromOnlineEvent, false);
window.addEventListener('offline', pollOnceFromOnlineEvent, false);
cordova.addWindowEventHandler('online');
cordova.addWindowEventHandler('offline');
document.addEventListener('online', proxyEvent, false);
document.addEventListener('offline', proxyEvent, false);
}
hookOnlineApis();
androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;
androidExec.setJsToNativeBridgeMode = function(mode) {
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
mode = jsToNativeModes.PROMPT;
}
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
jsToNativeBridgeMode = mode;
};
androidExec.setNativeToJsBridgeMode = function(mode) {
if (mode == nativeToJsBridgeMode) {
return;
}
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
pollEnabled = false;
}
nativeToJsBridgeMode = mode;
// Tell the native side to switch modes.
// Otherwise, it will be set by androidExec.init()
if (bridgeSecret >= 0) {
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
}
if (mode == nativeToJsModes.POLLING) {
pollEnabled = true;
setTimeout(pollingTimerFunc, 1);
}
};
function buildPayload(payload, message) {
var payloadKind = message.charAt(0);
if (payloadKind == 's') {
payload.push(message.slice(1));
} else if (payloadKind == 't') {
payload.push(true);
} else if (payloadKind == 'f') {
payload.push(false);
} else if (payloadKind == 'N') {
payload.push(null);
} else if (payloadKind == 'n') {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
payload.push(base64.toArrayBuffer(data));
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
var multipartMessages = message.slice(1);
while (multipartMessages !== "") {
var spaceIdx = multipartMessages.indexOf(' ');
var msgLen = +multipartMessages.slice(0, spaceIdx);
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
buildPayload(payload, multipartMessage);
}
} else {
payload.push(JSON.parse(message));
}
}
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
var firstChar = message.charAt(0);
if (firstChar == 'J') {
// This is deprecated on the .java side. It doesn't work with CSP enabled.
eval(message.slice(1));
} else if (firstChar == 'S' || firstChar == 'F') {
var success = firstChar == 'S';
var keepCallback = message.charAt(1) == '1';
var spaceIdx = message.indexOf(' ', 2);
var status = +message.slice(2, spaceIdx);
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
var payloadMessage = message.slice(nextSpaceIdx + 1);
var payload = [];
buildPayload(payload, payloadMessage);
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
} else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
}
}
function processMessages() {
// Check for the reentrant case.
if (isProcessing) {
return;
}
if (messagesFromNative.length === 0) {
return;
}
isProcessing = true;
try {
var msg = popMessageFromQueue();
// The Java side can send a * message to indicate that it
// still has messages waiting to be retrieved.
if (msg == '*' && messagesFromNative.length === 0) {
nextTick(pollOnce);
return;
}
processMessage(msg);
} finally {
isProcessing = false;
if (messagesFromNative.length > 0) {
nextTick(processMessages);
}
}
}
function popMessageFromQueue() {
var messageBatch = messagesFromNative.shift();
if (messageBatch == '*') {
return '*';
}
var spaceIdx = messageBatch.indexOf(' ');
var msgLen = +messageBatch.slice(0, spaceIdx);
var message = messageBatch.substr(spaceIdx + 1, msgLen);
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
if (messageBatch) {
messagesFromNative.unshift(messageBatch);
}
return message;
}
module.exports = androidExec;

View File

@ -0,0 +1,125 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// The last resume event that was received that had the result of a plugin call.
var lastResumeEvent = null;
module.exports = {
id: 'android',
bootstrap: function() {
var channel = require('cordova/channel'),
cordova = require('cordova'),
exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper');
// Get the shared secret needed to use the bridge.
exec.init();
// TODO: Extract this as a proper plugin.
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
// Inject a listener for the backbutton on the document.
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
backButtonChannel.onHasSubscribersChange = function() {
// If we just attached the first handler or detached the last handler,
// let native know we need to override the back button.
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
};
// Add hardware MENU and SEARCH button handlers
cordova.addDocumentEventHandler('menubutton');
cordova.addDocumentEventHandler('searchbutton');
function bindButtonChannel(buttonName) {
// generic button bind used for volumeup/volumedown buttons
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
volumeButtonChannel.onHasSubscribersChange = function() {
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
};
}
// Inject a listener for the volume buttons on the document.
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
// The resume event is not "sticky", but it is possible that the event
// will contain the result of a plugin call. We need to ensure that the
// plugin result is delivered even after the event is fired (CB-10498)
var cordovaAddEventListener = document.addEventListener;
document.addEventListener = function(evt, handler, capture) {
cordovaAddEventListener(evt, handler, capture);
if (evt === 'resume' && lastResumeEvent) {
handler(lastResumeEvent);
}
};
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
exec(null, null, APP_PLUGIN_NAME, "show", []);
});
}
};
function onMessageFromNative(msg) {
var cordova = require('cordova');
var action = msg.action;
switch (action)
{
// Button events
case 'backbutton':
case 'menubutton':
case 'searchbutton':
// App life cycle events
case 'pause':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
case 'resume':
if(arguments.length > 1 && msg.pendingResult) {
if(arguments.length === 2) {
msg.pendingResult.result = arguments[1];
} else {
// The plugin returned a multipart message
var res = [];
for(var i = 1; i < arguments.length; i++) {
res.push(arguments[i]);
}
msg.pendingResult.result = res;
}
// Save the plugin result so that it can be delivered to the js
// even if they miss the initial firing of the event
lastResumeEvent = msg;
}
cordova.fireDocumentEvent(action, msg);
break;
default:
throw new Error('Unknown event action ' + action);
}
}

View File

@ -0,0 +1,108 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var APP_PLUGIN_NAME = Number(require('cordova').platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
module.exports = {
/**
* Clear the resource cache.
*/
clearCache:function() {
exec(null, null, APP_PLUGIN_NAME, "clearCache", []);
},
/**
* Load the url into the webview or into new browser instance.
*
* @param url The URL to load
* @param props Properties that can be passed in to the activity:
* wait: int => wait msec before loading URL
* loadingDialog: "Title,Message" => display a native loading dialog
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
* clearHistory: boolean => clear webview history (default=false)
* openExternal: boolean => open in a new browser (default=false)
*
* Example:
* navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
*/
loadUrl:function(url, props) {
exec(null, null, APP_PLUGIN_NAME, "loadUrl", [url, props]);
},
/**
* Cancel loadUrl that is waiting to be loaded.
*/
cancelLoadUrl:function() {
exec(null, null, APP_PLUGIN_NAME, "cancelLoadUrl", []);
},
/**
* Clear web history in this web view.
* Instead of BACK button loading the previous web page, it will exit the app.
*/
clearHistory:function() {
exec(null, null, APP_PLUGIN_NAME, "clearHistory", []);
},
/**
* Go to previous page displayed.
* This is the same as pressing the backbutton on Android device.
*/
backHistory:function() {
exec(null, null, APP_PLUGIN_NAME, "backHistory", []);
},
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* Note: The user should not have to call this method. Instead, when the user
* registers for the "backbutton" event, this is automatically done.
*
* @param override T=override, F=cancel override
*/
overrideBackbutton:function(override) {
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [override]);
},
/**
* Override the default behavior of the Android volume button.
* If overridden, when the volume button is pressed, the "volume[up|down]button"
* JavaScript event will be fired.
*
* Note: The user should not have to call this method. Instead, when the user
* registers for the "volume[up|down]button" event, this is automatically done.
*
* @param button volumeup, volumedown
* @param override T=override, F=cancel override
*/
overrideButton:function(button, override) {
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [button, override]);
},
/**
* Exit and terminate the application.
*/
exitApp:function() {
return exec(null, null, APP_PLUGIN_NAME, "exitApp", []);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [
{
"id": "cordova-plugin-camera.Camera",
"file": "plugins/cordova-plugin-camera/www/CameraConstants.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"Camera"
]
},
{
"id": "cordova-plugin-camera.CameraPopoverOptions",
"file": "plugins/cordova-plugin-camera/www/CameraPopoverOptions.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"CameraPopoverOptions"
]
},
{
"id": "cordova-plugin-camera.camera",
"file": "plugins/cordova-plugin-camera/www/Camera.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"navigator.camera"
]
},
{
"id": "cordova-plugin-camera.CameraPopoverHandle",
"file": "plugins/cordova-plugin-camera/www/CameraPopoverHandle.js",
"pluginId": "cordova-plugin-camera",
"clobbers": [
"CameraPopoverHandle"
]
},
{
"id": "cordova-plugin-dialogs.notification",
"file": "plugins/cordova-plugin-dialogs/www/notification.js",
"pluginId": "cordova-plugin-dialogs",
"merges": [
"navigator.notification"
]
},
{
"id": "cordova-plugin-dialogs.notification_android",
"file": "plugins/cordova-plugin-dialogs/www/android/notification.js",
"pluginId": "cordova-plugin-dialogs",
"merges": [
"navigator.notification"
]
},
{
"id": "phonegap-plugin-barcodescanner.BarcodeScanner",
"file": "plugins/phonegap-plugin-barcodescanner/www/barcodescanner.js",
"pluginId": "phonegap-plugin-barcodescanner",
"clobbers": [
"cordova.plugins.barcodeScanner"
]
},
{
"id": "phonegap-plugin-local-notification.Notification",
"file": "plugins/phonegap-plugin-local-notification/www/notification.js",
"pluginId": "phonegap-plugin-local-notification",
"clobbers": [
"Notification"
]
}
];
module.exports.metadata =
// TOP OF METADATA
{
"cordova-plugin-camera": "4.0.3",
"cordova-plugin-dialogs": "2.0.1",
"cordova-plugin-whitelist": "1.3.3",
"phonegap-plugin-barcodescanner": "8.0.0",
"phonegap-plugin-local-notification": "1.0.1"
};
// BOTTOM OF METADATA
});

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Basic Setup</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="cordova.js"></script>
<link href="bundle.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="main.js"></script></body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,188 @@
cordova.define("cordova-plugin-camera.camera", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck');
var exec = require('cordova/exec');
var Camera = require('./Camera');
// XXX: commented out
// CameraPopoverHandle = require('./CameraPopoverHandle');
/**
* @namespace navigator
*/
/**
* @exports camera
*/
var cameraExport = {};
// Tack on the Camera Constants to the base camera plugin.
for (var key in Camera) {
cameraExport[key] = Camera[key];
}
/**
* Callback function that provides an error message.
* @callback module:camera.onError
* @param {string} message - The message is provided by the device's native code.
*/
/**
* Callback function that provides the image data.
* @callback module:camera.onSuccess
* @param {string} imageData - Base64 encoding of the image data, _or_ the image file URI, depending on [`cameraOptions`]{@link module:camera.CameraOptions} in effect.
* @example
* // Show image
* //
* function cameraCallback(imageData) {
* var image = document.getElementById('myImage');
* image.src = "data:image/jpeg;base64," + imageData;
* }
*/
/**
* Optional parameters to customize the camera settings.
* * [Quirks](#CameraOptions-quirks)
* @typedef module:camera.CameraOptions
* @type {Object}
* @property {number} [quality=50] - Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. (Note that information about the camera's resolution is unavailable.)
* @property {module:Camera.DestinationType} [destinationType=FILE_URI] - Choose the format of the return value.
* @property {module:Camera.PictureSourceType} [sourceType=CAMERA] - Set the source of the picture.
* @property {Boolean} [allowEdit=false] - Allow simple editing of image before selection.
* @property {module:Camera.EncodingType} [encodingType=JPEG] - Choose the returned image file's encoding.
* @property {number} [targetWidth] - Width in pixels to scale image. Must be used with `targetHeight`. Aspect ratio remains constant.
* @property {number} [targetHeight] - Height in pixels to scale image. Must be used with `targetWidth`. Aspect ratio remains constant.
* @property {module:Camera.MediaType} [mediaType=PICTURE] - Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`.
* @property {Boolean} [correctOrientation] - Rotate the image to correct for the orientation of the device during capture.
* @property {Boolean} [saveToPhotoAlbum] - Save the image to the photo album on the device after capture.
* @property {module:CameraPopoverOptions} [popoverOptions] - iOS-only options that specify popover location in iPad.
* @property {module:Camera.Direction} [cameraDirection=BACK] - Choose the camera to use (front- or back-facing).
*/
/**
* @description Takes a photo using the camera, or retrieves a photo from the device's
* image gallery. The image is passed to the success callback as a
* Base64-encoded `String`, or as the URI for the image file.
*
* The `camera.getPicture` function opens the device's default camera
* application that allows users to snap pictures by default - this behavior occurs,
* when `Camera.sourceType` equals [`Camera.PictureSourceType.CAMERA`]{@link module:Camera.PictureSourceType}.
* Once the user snaps the photo, the camera application closes and the application is restored.
*
* If `Camera.sourceType` is `Camera.PictureSourceType.PHOTOLIBRARY` or
* `Camera.PictureSourceType.SAVEDPHOTOALBUM`, then a dialog displays
* that allows users to select an existing image.
*
* The return value is sent to the [`cameraSuccess`]{@link module:camera.onSuccess} callback function, in
* one of the following formats, depending on the specified
* `cameraOptions`:
*
* - A `String` containing the Base64-encoded photo image.
* - A `String` representing the image file location on local storage (default).
*
* You can do whatever you want with the encoded image or URI, for
* example:
*
* - Render the image in an `<img>` tag, as in the example below
* - Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
* - Post the data to a remote server
*
* __NOTE__: Photo resolution on newer devices is quite good. Photos
* selected from the device's gallery are not downscaled to a lower
* quality, even if a `quality` parameter is specified. To avoid common
* memory problems, set `Camera.destinationType` to `FILE_URI` rather
* than `DATA_URL`.
*
* __Supported Platforms__
*
* - Android
* - BlackBerry
* - Browser
* - Firefox
* - FireOS
* - iOS
* - Windows
* - WP8
* - Ubuntu
*
* More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
*
* @example
* navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
* @param {module:camera.onSuccess} successCallback
* @param {module:camera.onError} errorCallback
* @param {module:camera.CameraOptions} options CameraOptions
*/
cameraExport.getPicture = function (successCallback, errorCallback, options) {
argscheck.checkArgs('fFO', 'Camera.getPicture', arguments);
options = options || {};
var getValue = argscheck.getValue;
var quality = getValue(options.quality, 50);
var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI);
var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA);
var targetWidth = getValue(options.targetWidth, -1);
var targetHeight = getValue(options.targetHeight, -1);
var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG);
var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE);
var allowEdit = !!options.allowEdit;
var correctOrientation = !!options.correctOrientation;
var saveToPhotoAlbum = !!options.saveToPhotoAlbum;
var popoverOptions = getValue(options.popoverOptions, null);
var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);
// XXX: commented out
// return new CameraPopoverHandle();
};
/**
* Removes intermediate image files that are kept in temporary storage
* after calling [`camera.getPicture`]{@link module:camera.getPicture}. Applies only when the value of
* `Camera.sourceType` equals `Camera.PictureSourceType.CAMERA` and the
* `Camera.destinationType` equals `Camera.DestinationType.FILE_URI`.
*
* __Supported Platforms__
*
* - iOS
*
* @example
* navigator.camera.cleanup(onSuccess, onFail);
*
* function onSuccess() {
* console.log("Camera cleanup success.")
* }
*
* function onFail(message) {
* alert('Failed because: ' + message);
* }
*/
cameraExport.cleanup = function (successCallback, errorCallback) {
exec(successCallback, errorCallback, 'Camera', 'cleanup', []);
};
module.exports = cameraExport;
});

View File

@ -0,0 +1,104 @@
cordova.define("cordova-plugin-camera.Camera", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* @module Camera
*/
module.exports = {
/**
* @description
* Defines the output format of `Camera.getPicture` call.
* _Note:_ On iOS passing `DestinationType.NATIVE_URI` along with
* `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM` will
* disable any image modifications (resize, quality change, cropping, etc.) due
* to implementation specific.
*
* @enum {number}
*/
DestinationType: {
/** Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI or NATIVE_URI if possible */
DATA_URL: 0,
/** Return file uri (content://media/external/images/media/2 for Android) */
FILE_URI: 1,
/** Return native uri (eg. asset-library://... for iOS) */
NATIVE_URI: 2
},
/**
* @enum {number}
*/
EncodingType: {
/** Return JPEG encoded image */
JPEG: 0,
/** Return PNG encoded image */
PNG: 1
},
/**
* @enum {number}
*/
MediaType: {
/** Allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType */
PICTURE: 0,
/** Allow selection of video only, ONLY RETURNS URL */
VIDEO: 1,
/** Allow selection from all media types */
ALLMEDIA: 2
},
/**
* @description
* Defines the output format of `Camera.getPicture` call.
* _Note:_ On iOS passing `PictureSourceType.PHOTOLIBRARY` or `PictureSourceType.SAVEDPHOTOALBUM`
* along with `DestinationType.NATIVE_URI` will disable any image modifications (resize, quality
* change, cropping, etc.) due to implementation specific.
*
* @enum {number}
*/
PictureSourceType: {
/** Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) */
PHOTOLIBRARY: 0,
/** Take picture from camera */
CAMERA: 1,
/** Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) */
SAVEDPHOTOALBUM: 2
},
/**
* Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
* @enum {number}
*/
PopoverArrowDirection: {
ARROW_UP: 1,
ARROW_DOWN: 2,
ARROW_LEFT: 4,
ARROW_RIGHT: 8,
ARROW_ANY: 15
},
/**
* @enum {number}
*/
Direction: {
/** Use the back-facing camera */
BACK: 0,
/** Use the front-facing camera */
FRONT: 1
}
};
});

View File

@ -0,0 +1,35 @@
cordova.define("cordova-plugin-camera.CameraPopoverHandle", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* @ignore in favour of iOS' one
* A handle to an image picker popover.
*/
var CameraPopoverHandle = function () {
this.setPosition = function (popoverOptions) {
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
};
};
module.exports = CameraPopoverHandle;
});

View File

@ -0,0 +1,55 @@
cordova.define("cordova-plugin-camera.CameraPopoverOptions", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var Camera = require('./Camera');
/**
* @namespace navigator
*/
/**
* iOS-only parameters that specify the anchor element location and arrow
* direction of the popover when selecting images from an iPad's library
* or album.
* Note that the size of the popover may change to adjust to the
* direction of the arrow and orientation of the screen. Make sure to
* account for orientation changes when specifying the anchor element
* location.
* @module CameraPopoverOptions
* @param {Number} [x=0] - x pixel coordinate of screen element onto which to anchor the popover.
* @param {Number} [y=32] - y pixel coordinate of screen element onto which to anchor the popover.
* @param {Number} [width=320] - width, in pixels, of the screen element onto which to anchor the popover.
* @param {Number} [height=480] - height, in pixels, of the screen element onto which to anchor the popover.
* @param {module:Camera.PopoverArrowDirection} [arrowDir=ARROW_ANY] - Direction the arrow on the popover should point.
*/
var CameraPopoverOptions = function (x, y, width, height, arrowDir) {
// information of rectangle that popover should be anchored to
this.x = x || 0;
this.y = y || 32;
this.width = width || 320;
this.height = height || 480;
this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY;
};
module.exports = CameraPopoverOptions;
});

View File

@ -0,0 +1,77 @@
cordova.define("cordova-plugin-dialogs.notification_android", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
/**
* Provides Android enhanced notification API.
*/
module.exports = {
activityStart: function (title, message) {
// If title and message not specified then mimic Android behavior of
// using default strings.
if (typeof title === 'undefined' && typeof message === 'undefined') {
title = 'Busy';
message = 'Please wait...';
}
exec(null, null, 'Notification', 'activityStart', [ title, message ]);
},
/**
* Close an activity dialog
*/
activityStop: function () {
exec(null, null, 'Notification', 'activityStop', []);
},
/**
* Display a progress dialog with progress bar that goes from 0 to 100.
*
* @param {String}
* title Title of the progress dialog.
* @param {String}
* message Message to display in the dialog.
*/
progressStart: function (title, message) {
exec(null, null, 'Notification', 'progressStart', [ title, message ]);
},
/**
* Close the progress dialog.
*/
progressStop: function () {
exec(null, null, 'Notification', 'progressStop', []);
},
/**
* Set the progress dialog value.
*
* @param {Number}
* value 0-100
*/
progressValue: function (value) {
exec(null, null, 'Notification', 'progressValue', [ value ]);
}
};
});

View File

@ -0,0 +1,133 @@
cordova.define("cordova-plugin-dialogs.notification", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var platform = require('cordova/platform');
/**
* Provides access to notifications on the device.
*/
module.exports = {
/**
* Open a native alert dialog, with a customizable title and button text.
*
* @param {String} message Message to print in the body of the alert
* @param {Function} completeCallback The callback that is called when user clicks on a button.
* @param {String} title Title of the alert dialog (default: Alert)
* @param {String} buttonLabel Label of the close button (default: OK)
*/
alert: function (message, completeCallback, title, buttonLabel) {
var _message = (typeof message === 'string' ? message : JSON.stringify(message));
var _title = (typeof title === 'string' ? title : 'Alert');
var _buttonLabel = (buttonLabel && typeof buttonLabel === 'string' ? buttonLabel : 'OK');
exec(completeCallback, null, 'Notification', 'alert', [_message, _title, _buttonLabel]);
},
/**
* Open a native confirm dialog, with a customizable title and button text.
* The result that the user selects is returned to the result callback.
*
* @param {String} message Message to print in the body of the alert
* @param {Function} resultCallback The callback that is called when user clicks on a button.
* @param {String} title Title of the alert dialog (default: Confirm)
* @param {Array} buttonLabels Array of the labels of the buttons (default: ['OK', 'Cancel'])
*/
confirm: function (message, resultCallback, title, buttonLabels) {
var _message = (typeof message === 'string' ? message : JSON.stringify(message));
var _title = (typeof title === 'string' ? title : 'Confirm');
var _buttonLabels = (buttonLabels || ['OK', 'Cancel']);
// Strings are deprecated!
if (typeof _buttonLabels === 'string') {
console.log('Notification.confirm(string, function, string, string) is deprecated. Use Notification.confirm(string, function, string, array).');
}
_buttonLabels = convertButtonLabels(_buttonLabels);
exec(resultCallback, null, 'Notification', 'confirm', [_message, _title, _buttonLabels]);
},
/**
* Open a native prompt dialog, with a customizable title and button text.
* The following results are returned to the result callback:
* buttonIndex Index number of the button selected.
* input1 The text entered in the prompt dialog box.
*
* @param {String} message Dialog message to display (default: "Prompt message")
* @param {Function} resultCallback The callback that is called when user clicks on a button.
* @param {String} title Title of the dialog (default: "Prompt")
* @param {Array} buttonLabels Array of strings for the button labels (default: ["OK","Cancel"])
* @param {String} defaultText Textbox input value (default: empty string)
*/
prompt: function (message, resultCallback, title, buttonLabels, defaultText) {
var _message = (typeof message === 'string' ? message : JSON.stringify(message));
var _title = (typeof title === 'string' ? title : 'Prompt');
var _buttonLabels = (buttonLabels || ['OK', 'Cancel']);
// Strings are deprecated!
if (typeof _buttonLabels === 'string') {
console.log('Notification.prompt(string, function, string, string) is deprecated. Use Notification.confirm(string, function, string, array).');
}
_buttonLabels = convertButtonLabels(_buttonLabels);
var _defaultText = (defaultText || '');
exec(resultCallback, null, 'Notification', 'prompt', [_message, _title, _buttonLabels, _defaultText]);
},
/**
* Causes the device to beep.
* On Android, the default notification ringtone is played "count" times.
*
* @param {Integer} count The number of beeps.
*/
beep: function (count) {
var defaultedCount = count || 1;
exec(null, null, 'Notification', 'beep', [ defaultedCount ]);
}
};
function convertButtonLabels (buttonLabels) {
// Some platforms take an array of button label names.
// Other platforms take a comma separated list.
// For compatibility, we convert to the desired type based on the platform.
if (platform.id === 'amazon-fireos' || platform.id === 'android' || platform.id === 'ios' ||
platform.id === 'windowsphone' || platform.id === 'firefoxos' || platform.id === 'ubuntu' ||
platform.id === 'windows8' || platform.id === 'windows') {
if (typeof buttonLabels === 'string') {
buttonLabels = buttonLabels.split(','); // not crazy about changing the var type here
}
} else {
if (Array.isArray(buttonLabels)) {
var buttonLabelArray = buttonLabels;
buttonLabels = buttonLabelArray.toString();
}
}
return buttonLabels;
}
});

View File

@ -0,0 +1,159 @@
cordova.define("phonegap-plugin-barcodescanner.BarcodeScanner", function(require, exports, module) {
/**
* cordova is available under the MIT License (2008).
* See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) Matt Kane 2010
* Copyright (c) 2011, IBM Corporation
* Copyright (c) 2012-2017, Adobe Systems
*/
var exec = cordova.require("cordova/exec");
var scanInProgress = false;
/**
* Constructor.
*
* @returns {BarcodeScanner}
*/
function BarcodeScanner() {
/**
* Encoding constants.
*
* @type Object
*/
this.Encode = {
TEXT_TYPE: "TEXT_TYPE",
EMAIL_TYPE: "EMAIL_TYPE",
PHONE_TYPE: "PHONE_TYPE",
SMS_TYPE: "SMS_TYPE"
// CONTACT_TYPE: "CONTACT_TYPE", // TODO: not implemented, requires passing a Bundle class from Javascript to Java
// LOCATION_TYPE: "LOCATION_TYPE" // TODO: not implemented, requires passing a Bundle class from Javascript to Java
};
/**
* Barcode format constants, defined in ZXing library.
*
* @type Object
*/
this.format = {
"all_1D": 61918,
"aztec": 1,
"codabar": 2,
"code_128": 16,
"code_39": 4,
"code_93": 8,
"data_MATRIX": 32,
"ean_13": 128,
"ean_8": 64,
"itf": 256,
"maxicode": 512,
"msi": 131072,
"pdf_417": 1024,
"plessey": 262144,
"qr_CODE": 2048,
"rss_14": 4096,
"rss_EXPANDED": 8192,
"upc_A": 16384,
"upc_E": 32768,
"upc_EAN_EXTENSION": 65536
};
}
/**
* Read code from scanner.
*
* @param {Function} successCallback This function will recieve a result object: {
* text : '12345-mock', // The code that was scanned.
* format : 'FORMAT_NAME', // Code format.
* cancelled : true/false, // Was canceled.
* }
* @param {Function} errorCallback
* @param config
*/
BarcodeScanner.prototype.scan = function (successCallback, errorCallback, config) {
if (config instanceof Array) {
// do nothing
} else {
if (typeof(config) === 'object') {
// string spaces between formats, ZXing does not like that
if (config.formats) {
config.formats = config.formats.replace(/\s+/g, '');
}
config = [ config ];
} else {
config = [];
}
}
if (errorCallback == null) {
errorCallback = function () {
};
}
if (typeof errorCallback != "function") {
console.log("BarcodeScanner.scan failure: failure parameter not a function");
return;
}
if (typeof successCallback != "function") {
console.log("BarcodeScanner.scan failure: success callback parameter must be a function");
return;
}
if (scanInProgress) {
errorCallback('Scan is already in progress');
return;
}
scanInProgress = true;
exec(
function(result) {
scanInProgress = false;
// work around bug in ZXing library
if (result.format === 'UPC_A' && result.text.length === 13) {
result.text = result.text.substring(1);
}
successCallback(result);
},
function(error) {
scanInProgress = false;
errorCallback(error);
},
'BarcodeScanner',
'scan',
config
);
};
//-------------------------------------------------------------------
BarcodeScanner.prototype.encode = function (type, data, successCallback, errorCallback, options) {
if (errorCallback == null) {
errorCallback = function () {
};
}
if (typeof errorCallback != "function") {
console.log("BarcodeScanner.encode failure: failure parameter not a function");
return;
}
if (typeof successCallback != "function") {
console.log("BarcodeScanner.encode failure: success callback parameter must be a function");
return;
}
exec(successCallback, errorCallback, 'BarcodeScanner', 'encode', [
{"type": type, "data": data, "options": options}
]);
};
var barcodeScanner = new BarcodeScanner();
module.exports = barcodeScanner;
});

View File

@ -0,0 +1,108 @@
cordova.define("phonegap-plugin-local-notification.Notification", function(require, exports, module) {
/* global cordova:false */
/* globals window */
var argscheck = cordova.require('cordova/argscheck'),
exec = cordova.require('cordova/exec'),
utils = cordova.require('cordova/utils');
/**
* @description A global object that lets you interact with the Notification API.
* @global
* @param {!string} title of the local notification.
* @param {?Options} options An object containing optional property/value pairs.
*/
var Notification = function(title, options) {
// require title parameter
if (typeof title === 'undefined') {
throw new Error('The title argument is required.');
}
options = options || {};
var getValue = argscheck.getValue;
this.permission = 'granted';
this.title = getValue(title, '');
this.dir = getValue(options.dir, 'auto');
this.lang = getValue(options.lang, '');
this.body = getValue(options.body, '');
this.tag = getValue(options.tag, '');
this.icon = getValue(options.icon, '');
this.onclick = function() {};
this.onshow = function() {};
this.onerror = function() {};
this.onclose = function() {};
// triggered on click, show, error and close
var that = this;
var success = function(result) {
if (!result) {
return;
}
if (result === 'show') {
that.onshow();
} else if (result === 'click') {
that.onclick();
}
};
var failure = function() {
that.onerror();
};
exec(success, failure, 'LocalNotifications', 'show', [this.title, this.dir, this.lang, this.body, this.tag, this.icon]);
};
/**
* @description requests permission from the user to show a local notification.
* @function requestPermission
* @memberof Notification
* @param {!callback} callback - See type definition.
*/
Notification.requestPermission = function(callback) {
if (!callback) { callback = function() {}; }
if (typeof callback !== 'function') {
console.log('Notification.requestPermission failure: callback parameter not a function');
return;
}
exec(callback, function() {
console.log('requestPermission error');
}, 'LocalNotifications', 'requestPermission', []);
};
/**
* @description closes an open notification.
* @function close
* @memberof Notification
*/
Notification.prototype.close = function() {
var that = this;
exec(function() {
that.onclose();
}, function() {
that.onerror();
}, 'LocalNotifications', 'close', [this.tag]);
};
/**
* @description A callback to be used when the requestPermission method returns a value.
*
* @callback callback
* @param {string} permission - one of "default", "denied" or "granted"
*/
/*
* @typedef {Object} Options - An object for configuring Notification behavior.
* @property {string} [dir='auto'] - Sets the direction of the notification. One of "auto", "ltr" or "rtl"
* @property {string} [lang=''] - Sets the language of the notification
* @property {string} [body=''] - Sets the body of the notification
* @property {string} [tag=''] - Sets the identifying tag of the notification
* @property {string} [icon=''] - Sets the icon of the notification
*/
module.exports = Notification;
});

View File

@ -0,0 +1,130 @@
package com.adobe.phonegap.notification;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Random;
/**
* This class exposes methods in Cordova that can be called from JavaScript.
*/
public class LocalNotifications extends CordovaPlugin {
private static final String TAG = "LocalNotifications";
private static CallbackContext notificationContext;
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context from which we were invoked.
*/
@SuppressLint("NewApi")
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "in local notifications");
if (action.equals("show")) {
Log.d(TAG, "action show");
notificationContext = callbackContext;
showNotification(args);
PluginResult result = new PluginResult(PluginResult.Status.OK, "show");
result.setKeepCallback(true);
notificationContext.sendPluginResult(result);
} else if (action.equals("close")) {
NotificationManager mNotificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.cancel(args.getString(0), 0);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
} else if (action.equals("requestPermission")) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, "granted"));
} else {
Log.d(TAG, "return false");
return false;
}
return true;
}
private void showNotification(JSONArray args) throws JSONException {
// Get args
String title = args.getString(0);
String dir = args.getString(1);
String lang = args.getString(2);
String body = args.getString(3);
String tag = args.getString(4);
String icon = args.getString(5);
Context context = cordova.getActivity();
Intent notificationIntent = new Intent(context, NotificationHandlerActivity.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationIntent.putExtra("tag", tag);
int requestCode = new Random().nextInt();
PendingIntent contentIntent = PendingIntent.getActivity(context, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Build notifications
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context)
.setWhen(System.currentTimeMillis())
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(context.getApplicationInfo().icon)
.setContentIntent(contentIntent)
.setAutoCancel(true);
if (icon.startsWith("http://") || icon.startsWith("https://")) {
Bitmap bitmap = getBitmapFromURL(icon);
mBuilder.setLargeIcon(bitmap);
}
// Show notification
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(tag, 0, mBuilder.build());
}
private Bitmap getBitmapFromURL(String strURL) {
try {
URL url = new URL(strURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(15000);
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static void fireClickEvent(String tag) {
PluginResult result = new PluginResult(PluginResult.Status.OK, "click");
result.setKeepCallback(true);
notificationContext.sendPluginResult(result);
}
}

View File

@ -0,0 +1,48 @@
package com.adobe.phonegap.notification;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.support.v4.app.RemoteInput;
public class NotificationHandlerActivity extends Activity {
private static String LOG_TAG = "LocalNotification_PushHandlerActivity";
/*
* this activity will be started if the user touches a notification that we own.
* We send it's data off to the push plugin for processing.
* If needed, we boot up the main activity to kickstart the application.
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(LOG_TAG, "in on create of pushhandleractivity");
Intent intent = getIntent();
String tag = intent.getExtras().getString("tag", "");
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(tag, 0);
LocalNotifications.fireClickEvent(tag);
forceMainActivityReload();
finish();
}
/**
* Forces the main activity to re-launch if it's unloaded.
*/
private void forceMainActivityReload() {
PackageManager pm = getPackageManager();
Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
startActivity(launchIntent);
}
}

View File

@ -0,0 +1,328 @@
/**
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) Matt Kane 2010
* Copyright (c) 2011, IBM Corporation
* Copyright (c) 2013, Maciej Nux Jaros
*/
package com.phonegap.plugins.barcodescanner;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.content.pm.PackageManager;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PermissionHelper;
import com.google.zxing.client.android.CaptureActivity;
import com.google.zxing.client.android.encode.EncodeActivity;
import com.google.zxing.client.android.Intents;
/**
* This calls out to the ZXing barcode reader and returns the result.
*
* @sa https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CordovaPlugin.java
*/
public class BarcodeScanner extends CordovaPlugin {
public static final int REQUEST_CODE = 0x0ba7c;
private static final String SCAN = "scan";
private static final String ENCODE = "encode";
private static final String CANCELLED = "cancelled";
private static final String FORMAT = "format";
private static final String TEXT = "text";
private static final String DATA = "data";
private static final String TYPE = "type";
private static final String PREFER_FRONTCAMERA = "preferFrontCamera";
private static final String ORIENTATION = "orientation";
private static final String SHOW_FLIP_CAMERA_BUTTON = "showFlipCameraButton";
private static final String RESULTDISPLAY_DURATION = "resultDisplayDuration";
private static final String SHOW_TORCH_BUTTON = "showTorchButton";
private static final String TORCH_ON = "torchOn";
private static final String SAVE_HISTORY = "saveHistory";
private static final String DISABLE_BEEP = "disableSuccessBeep";
private static final String FORMATS = "formats";
private static final String PROMPT = "prompt";
private static final String TEXT_TYPE = "TEXT_TYPE";
private static final String EMAIL_TYPE = "EMAIL_TYPE";
private static final String PHONE_TYPE = "PHONE_TYPE";
private static final String SMS_TYPE = "SMS_TYPE";
private static final String LOG_TAG = "BarcodeScanner";
private String [] permissions = { Manifest.permission.CAMERA };
private JSONArray requestArgs;
private CallbackContext callbackContext;
/**
* Constructor.
*/
public BarcodeScanner() {
}
/**
* Executes the request.
*
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
* cordova.getThreadPool().execute(runnable);
*
* To run on the UI thread, use:
* cordova.getActivity().runOnUiThread(runnable);
*
* @param action The action to execute.
* @param args The exec() arguments.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return Whether the action was valid.
*
* @sa https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CordovaPlugin.java
*/
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
this.requestArgs = args;
if (action.equals(ENCODE)) {
JSONObject obj = args.optJSONObject(0);
if (obj != null) {
String type = obj.optString(TYPE);
String data = obj.optString(DATA);
// If the type is null then force the type to text
if (type == null) {
type = TEXT_TYPE;
}
if (data == null) {
callbackContext.error("User did not specify data to encode");
return true;
}
encode(type, data);
} else {
callbackContext.error("User did not specify data to encode");
return true;
}
} else if (action.equals(SCAN)) {
//android permission auto add
if(!hasPermisssion()) {
requestPermissions(0);
} else {
scan(args);
}
} else {
return false;
}
return true;
}
/**
* Starts an intent to scan and decode a barcode.
*/
public void scan(final JSONArray args) {
final CordovaPlugin that = this;
cordova.getThreadPool().execute(new Runnable() {
public void run() {
Intent intentScan = new Intent(that.cordova.getActivity().getBaseContext(), CaptureActivity.class);
intentScan.setAction(Intents.Scan.ACTION);
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// add config as intent extras
if (args.length() > 0) {
JSONObject obj;
JSONArray names;
String key;
Object value;
for (int i = 0; i < args.length(); i++) {
try {
obj = args.getJSONObject(i);
} catch (JSONException e) {
Log.i("CordovaLog", e.getLocalizedMessage());
continue;
}
names = obj.names();
for (int j = 0; j < names.length(); j++) {
try {
key = names.getString(j);
value = obj.get(key);
if (value instanceof Integer) {
intentScan.putExtra(key, (Integer) value);
} else if (value instanceof String) {
intentScan.putExtra(key, (String) value);
}
} catch (JSONException e) {
Log.i("CordovaLog", e.getLocalizedMessage());
}
}
intentScan.putExtra(Intents.Scan.CAMERA_ID, obj.optBoolean(PREFER_FRONTCAMERA, false) ? 1 : 0);
intentScan.putExtra(Intents.Scan.SHOW_FLIP_CAMERA_BUTTON, obj.optBoolean(SHOW_FLIP_CAMERA_BUTTON, false));
intentScan.putExtra(Intents.Scan.SHOW_TORCH_BUTTON, obj.optBoolean(SHOW_TORCH_BUTTON, false));
intentScan.putExtra(Intents.Scan.TORCH_ON, obj.optBoolean(TORCH_ON, false));
intentScan.putExtra(Intents.Scan.SAVE_HISTORY, obj.optBoolean(SAVE_HISTORY, false));
boolean beep = obj.optBoolean(DISABLE_BEEP, false);
intentScan.putExtra(Intents.Scan.BEEP_ON_SCAN, !beep);
if (obj.has(RESULTDISPLAY_DURATION)) {
intentScan.putExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS, "" + obj.optLong(RESULTDISPLAY_DURATION));
}
if (obj.has(FORMATS)) {
intentScan.putExtra(Intents.Scan.FORMATS, obj.optString(FORMATS));
}
if (obj.has(PROMPT)) {
intentScan.putExtra(Intents.Scan.PROMPT_MESSAGE, obj.optString(PROMPT));
}
if (obj.has(ORIENTATION)) {
intentScan.putExtra(Intents.Scan.ORIENTATION_LOCK, obj.optString(ORIENTATION));
}
}
}
// avoid calling other phonegap apps
intentScan.setPackage(that.cordova.getActivity().getApplicationContext().getPackageName());
that.cordova.startActivityForResult(that, intentScan, REQUEST_CODE);
}
});
}
/**
* Called when the barcode scanner intent completes.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE && this.callbackContext != null) {
if (resultCode == Activity.RESULT_OK) {
JSONObject obj = new JSONObject();
try {
obj.put(TEXT, intent.getStringExtra("SCAN_RESULT"));
obj.put(FORMAT, intent.getStringExtra("SCAN_RESULT_FORMAT"));
obj.put(CANCELLED, false);
} catch (JSONException e) {
Log.d(LOG_TAG, "This should never happen");
}
//this.success(new PluginResult(PluginResult.Status.OK, obj), this.callback);
this.callbackContext.success(obj);
} else if (resultCode == Activity.RESULT_CANCELED) {
JSONObject obj = new JSONObject();
try {
obj.put(TEXT, "");
obj.put(FORMAT, "");
obj.put(CANCELLED, true);
} catch (JSONException e) {
Log.d(LOG_TAG, "This should never happen");
}
//this.success(new PluginResult(PluginResult.Status.OK, obj), this.callback);
this.callbackContext.success(obj);
} else {
//this.error(new PluginResult(PluginResult.Status.ERROR), this.callback);
this.callbackContext.error("Unexpected error");
}
}
}
/**
* Initiates a barcode encode.
*
* @param type Endoiding type.
* @param data The data to encode in the bar code.
*/
public void encode(String type, String data) {
Intent intentEncode = new Intent(this.cordova.getActivity().getBaseContext(), EncodeActivity.class);
intentEncode.setAction(Intents.Encode.ACTION);
intentEncode.putExtra(Intents.Encode.TYPE, type);
intentEncode.putExtra(Intents.Encode.DATA, data);
// avoid calling other phonegap apps
intentEncode.setPackage(this.cordova.getActivity().getApplicationContext().getPackageName());
this.cordova.getActivity().startActivity(intentEncode);
}
/**
* check application's permissions
*/
public boolean hasPermisssion() {
for(String p : permissions)
{
if(!PermissionHelper.hasPermission(this, p))
{
return false;
}
}
return true;
}
/**
* We override this so that we can access the permissions variable, which no longer exists in
* the parent class, since we can't initialize it reliably in the constructor!
*
* @param requestCode The code to get request action
*/
public void requestPermissions(int requestCode)
{
PermissionHelper.requestPermissions(this, requestCode, permissions);
}
/**
* processes the result of permission request
*
* @param requestCode The code to get request action
* @param permissions The collection of permissions
* @param grantResults The result of grant
*/
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException
{
PluginResult result;
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
Log.d(LOG_TAG, "Permission Denied!");
result = new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION);
this.callbackContext.sendPluginResult(result);
return;
}
}
switch(requestCode)
{
case 0:
scan(this.requestArgs);
break;
}
}
/**
* This plugin launches an external Activity when the camera is opened, so we
* need to implement the save/restore API in case the Activity gets killed
* by the OS while it's in the background.
*/
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
}
}

View File

@ -0,0 +1,41 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package io.nicco.app.fotm.cordova;
import android.os.Bundle;
import org.apache.cordova.*;
public class MainActivity extends CordovaActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// enable Cordova apps to be started in the background
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
moveTaskToBack(true);
}
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
}
}

View File

@ -0,0 +1,104 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.camera;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.FileProvider;
import java.io.File;
/*
* This class exists because Andorid FilesProvider doesn't work on Android 4.4.4 and below and throws
* weird errors. I'm not sure why writing to shared cache directories is somehow verboten, but it is
* and this error is irritating for a Compatibility library to have.
*
*/
public class CordovaUri {
private Uri androidUri;
private String fileName;
private Uri fileUri;
/*
* We always expect a FileProvider string to be passed in for the file that we create
*
*/
CordovaUri (Uri inputUri)
{
//Determine whether the file is a content or file URI
if(inputUri.getScheme().equals("content"))
{
androidUri = inputUri;
fileName = getFileNameFromUri(androidUri);
fileUri = Uri.parse("file://" + fileName);
}
else
{
fileUri = inputUri;
fileName = FileHelper.stripFileProtocol(inputUri.toString());
}
}
public Uri getFileUri()
{
return fileUri;
}
public String getFilePath()
{
return fileName;
}
/*
* This only gets called by takePicture
*/
public Uri getCorrectUri()
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return androidUri;
else
return fileUri;
}
/*
* This is dirty, but it does the job.
*
* Since the FilesProvider doesn't really provide you a way of getting a URL from the file,
* and since we actually need the Camera to create the file for us most of the time, we don't
* actually write the file, just generate the location based on a timestamp, we need to get it
* back from the Intent.
*
* However, the FilesProvider preserves the path, so we can at least write to it from here, since
* we own the context in this case.
*/
private String getFileNameFromUri(Uri uri) {
String fullUri = uri.toString();
String partial_path = fullUri.split("external_files")[1];
File external_storage = Environment.getExternalStorageDirectory();
String path = external_storage.getAbsolutePath() + partial_path;
return path;
}
}

Some files were not shown because too many files have changed in this diff Show More