Initial commit

This commit is contained in:
2018-09-22 10:44:38 +08:00
commit eacb95c18a
723 changed files with 100993 additions and 0 deletions

14
platforms/android/.gitignore vendored Normal file
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,24 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="10101" android:versionName="1.1.1" package="net.yunkong2.vis" 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>
</application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="25" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
</manifest>

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="14" />
</manifest>

View File

@@ -0,0 +1,135 @@
/* 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 {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
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 = '6.2.3'
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
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 = '6.2.3'
released = new Date()
vcsTag = '6.2.3'
}
}
}

View File

@@ -0,0 +1,201 @@
/*
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()
file('project.properties').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-25
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,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,518 @@
/*
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
*/
@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,182 @@
/*
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.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. */
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,96 @@
/*
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.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
*/
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
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
*/
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,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;
import android.app.Activity;
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.
*
* @return the Activity
*/
public abstract Activity getActivity();
/**
* 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,241 @@
/*
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.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 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);
}
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 = "6.2.3";
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,613 @@
/*
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.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());
}
@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,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,355 @@
/*
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) {
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);
// Enable scaling
// Fix for CB-12015
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(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();
}
}
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,578 @@
{
"prepare_queue": {
"installed": [],
"uninstalled": []
},
"config_munge": {
"files": {
"AndroidManifest.xml": {
"parents": {
"/manifest": [
{
"xml": "<uses-permission android:name=\"android.permission.VIBRATE\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
"count": 1
}
],
"/*": [
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
"count": 1
},
{
"xml": "<uses-feature android:name=\"android.hardware.location.gps\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />",
"count": 1
},
{
"xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
"count": 1
}
]
}
},
"res/xml/config.xml": {
"parents": {
"/*": [
{
"xml": "<feature name=\"SystemVolume\"><param name=\"android-package\" value=\"com.jiliac.systemvolume.SystemVolume\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"OrientationLock\"><param name=\"android-package\" value=\"com.plugin.phonegap.OrientationLock\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Geolocation\"><param name=\"android-package\" value=\"org.apache.cordova.geolocation.Geolocation\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Battery\"><param name=\"android-package\" value=\"org.apache.cordova.batterystatus.BatteryListener\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Toast\"><param name=\"android-package\" value=\"nl.xservices.plugins.Toast\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"AppVersion\"><param name=\"android-package\" value=\"uk.co.whiteoctober.cordova.AppVersion\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"SpeechRecognition\"><param name=\"android-package\" value=\"org.apache.cordova.speech.SpeechRecognition\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"NetworkStatus\"><param name=\"android-package\" value=\"org.apache.cordova.networkinformation.NetworkManager\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"Insomnia\"><param name=\"android-package\" value=\"nl.xservices.plugins.Insomnia\" /></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=\"AndroidFullScreen\"><param name=\"android-package\" value=\"com.mesmotronic.plugins.FullScreenPlugin\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"File\"><param name=\"android-package\" value=\"org.apache.cordova.file.FileUtils\" /><param name=\"onload\" value=\"true\" /></feature>",
"count": 1
},
{
"xml": "<allow-navigation href=\"cdvfile:*\" />",
"count": 1
},
{
"xml": "<feature name=\"WifiWizard\"><param name=\"android-package\" value=\"com.pylonproducts.wifiwizard.WifiWizard\" /><param name=\"onload\" value=\"true\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"CertificatesPlugin\"><param name=\"android-package\" value=\"de.martinreinhardt.cordova.plugins.CertificatesPlugin\" /></feature>",
"count": 1
}
]
}
},
"config.xml": {
"parents": {
"/*": [
{
"xml": "<feature name=\"TTS\"><param name=\"android-package\" value=\"com.wordsbaking.cordova.tts.TTS\" /><param name=\"onload\" value=\"true\" /></feature>",
"count": 1
},
{
"xml": "<feature name=\"ExitApp\"><param name=\"android-package\" value=\"cordova.custom.plugins.exitapp.ExitApp\" /></feature>",
"count": 1
}
]
}
}
}
},
"installed_plugins": {
"com.jiliac.systemvolume": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"com.phonegap.plugins.OrientationLock": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-tts": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova.custom.plugins.exitapp": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-compat": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-geolocation": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-battery-status": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-x-toast": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-app-version": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"phonegap-plugin-speech-recognition": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-network-information": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-insomnia": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-whitelist": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-fullscreen": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-file": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"com.pylonproducts.wifiwizard": {
"PACKAGE_NAME": "net.yunkong2.vis"
},
"cordova-plugin-certificates": {
"PACKAGE_NAME": "net.yunkong2.vis"
}
},
"dependent_plugins": {},
"modules": [
{
"id": "com.jiliac.systemvolume.SystemVolume",
"file": "plugins/com.jiliac.systemvolume/www/systemvolume.js",
"pluginId": "com.jiliac.systemvolume",
"clobbers": [
"window.system"
]
},
{
"id": "com.phonegap.plugins.OrientationLock.OrientationLock",
"file": "plugins/com.phonegap.plugins.OrientationLock/www/orientationLock.js",
"pluginId": "com.phonegap.plugins.OrientationLock",
"clobbers": [
"OrientationLock"
]
},
{
"id": "cordova-plugin-tts.tts",
"file": "plugins/cordova-plugin-tts/www/tts.js",
"pluginId": "cordova-plugin-tts",
"clobbers": [
"TTS"
]
},
{
"id": "cordova.custom.plugins.exitapp.exitApp",
"file": "plugins/cordova.custom.plugins.exitapp/www/ExitApp.js",
"pluginId": "cordova.custom.plugins.exitapp",
"merges": [
"navigator.app"
]
},
{
"id": "cordova-plugin-geolocation.geolocation",
"file": "plugins/cordova-plugin-geolocation/www/android/geolocation.js",
"pluginId": "cordova-plugin-geolocation",
"clobbers": [
"navigator.geolocation"
]
},
{
"id": "cordova-plugin-geolocation.PositionError",
"file": "plugins/cordova-plugin-geolocation/www/PositionError.js",
"pluginId": "cordova-plugin-geolocation",
"runs": true
},
{
"id": "cordova-plugin-battery-status.battery",
"file": "plugins/cordova-plugin-battery-status/www/battery.js",
"pluginId": "cordova-plugin-battery-status",
"clobbers": [
"navigator.battery"
]
},
{
"id": "cordova-plugin-x-toast.Toast",
"file": "plugins/cordova-plugin-x-toast/www/Toast.js",
"pluginId": "cordova-plugin-x-toast",
"clobbers": [
"window.plugins.toast"
]
},
{
"id": "cordova-plugin-x-toast.tests",
"file": "plugins/cordova-plugin-x-toast/test/tests.js",
"pluginId": "cordova-plugin-x-toast"
},
{
"id": "cordova-plugin-app-version.AppVersionPlugin",
"file": "plugins/cordova-plugin-app-version/www/AppVersionPlugin.js",
"pluginId": "cordova-plugin-app-version",
"clobbers": [
"cordova.getAppVersion"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognition",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognition.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognition"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognitionError",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognitionError.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognitionError"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognitionAlternative",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognitionAlternative.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognitionAlternative"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognitionResult",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognitionResult.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognitionResult"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognitionResultList",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognitionResultList.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognitionResultList"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechRecognitionEvent",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechRecognitionEvent.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechRecognitionEvent"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechGrammar",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechGrammar.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechGrammar"
]
},
{
"id": "phonegap-plugin-speech-recognition.SpeechGrammarList",
"file": "plugins/phonegap-plugin-speech-recognition/www/SpeechGrammarList.js",
"pluginId": "phonegap-plugin-speech-recognition",
"clobbers": [
"SpeechGrammarList"
]
},
{
"id": "cordova-plugin-network-information.network",
"file": "plugins/cordova-plugin-network-information/www/network.js",
"pluginId": "cordova-plugin-network-information",
"clobbers": [
"navigator.connection",
"navigator.network.connection"
]
},
{
"id": "cordova-plugin-network-information.Connection",
"file": "plugins/cordova-plugin-network-information/www/Connection.js",
"pluginId": "cordova-plugin-network-information",
"clobbers": [
"Connection"
]
},
{
"id": "cordova-plugin-insomnia.Insomnia",
"file": "plugins/cordova-plugin-insomnia/www/Insomnia.js",
"pluginId": "cordova-plugin-insomnia",
"clobbers": [
"window.plugins.insomnia"
]
},
{
"id": "cordova-plugin-fullscreen.AndroidFullScreen",
"file": "plugins/cordova-plugin-fullscreen/www/AndroidFullScreen.js",
"pluginId": "cordova-plugin-fullscreen",
"clobbers": [
"AndroidFullScreen"
]
},
{
"id": "cordova-plugin-file.DirectoryEntry",
"file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.DirectoryEntry"
]
},
{
"id": "cordova-plugin-file.DirectoryReader",
"file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.DirectoryReader"
]
},
{
"id": "cordova-plugin-file.Entry",
"file": "plugins/cordova-plugin-file/www/Entry.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.Entry"
]
},
{
"id": "cordova-plugin-file.File",
"file": "plugins/cordova-plugin-file/www/File.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.File"
]
},
{
"id": "cordova-plugin-file.FileEntry",
"file": "plugins/cordova-plugin-file/www/FileEntry.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileEntry"
]
},
{
"id": "cordova-plugin-file.FileError",
"file": "plugins/cordova-plugin-file/www/FileError.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileError"
]
},
{
"id": "cordova-plugin-file.FileReader",
"file": "plugins/cordova-plugin-file/www/FileReader.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileReader"
]
},
{
"id": "cordova-plugin-file.FileSystem",
"file": "plugins/cordova-plugin-file/www/FileSystem.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileSystem"
]
},
{
"id": "cordova-plugin-file.FileUploadOptions",
"file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileUploadOptions"
]
},
{
"id": "cordova-plugin-file.FileUploadResult",
"file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileUploadResult"
]
},
{
"id": "cordova-plugin-file.FileWriter",
"file": "plugins/cordova-plugin-file/www/FileWriter.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.FileWriter"
]
},
{
"id": "cordova-plugin-file.Flags",
"file": "plugins/cordova-plugin-file/www/Flags.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.Flags"
]
},
{
"id": "cordova-plugin-file.LocalFileSystem",
"file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.LocalFileSystem"
],
"merges": [
"window"
]
},
{
"id": "cordova-plugin-file.Metadata",
"file": "plugins/cordova-plugin-file/www/Metadata.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.Metadata"
]
},
{
"id": "cordova-plugin-file.ProgressEvent",
"file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.ProgressEvent"
]
},
{
"id": "cordova-plugin-file.fileSystems",
"file": "plugins/cordova-plugin-file/www/fileSystems.js",
"pluginId": "cordova-plugin-file"
},
{
"id": "cordova-plugin-file.requestFileSystem",
"file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
"pluginId": "cordova-plugin-file",
"clobbers": [
"window.requestFileSystem"
]
},
{
"id": "cordova-plugin-file.resolveLocalFileSystemURI",
"file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
"pluginId": "cordova-plugin-file",
"merges": [
"window"
]
},
{
"id": "cordova-plugin-file.isChrome",
"file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
"pluginId": "cordova-plugin-file",
"runs": true
},
{
"id": "cordova-plugin-file.androidFileSystem",
"file": "plugins/cordova-plugin-file/www/android/FileSystem.js",
"pluginId": "cordova-plugin-file",
"merges": [
"FileSystem"
]
},
{
"id": "cordova-plugin-file.fileSystems-roots",
"file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
"pluginId": "cordova-plugin-file",
"runs": true
},
{
"id": "cordova-plugin-file.fileSystemPaths",
"file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
"pluginId": "cordova-plugin-file",
"merges": [
"cordova"
],
"runs": true
},
{
"id": "com.pylonproducts.wifiwizard.WifiWizard",
"file": "plugins/com.pylonproducts.wifiwizard/www/WifiWizard.js",
"pluginId": "com.pylonproducts.wifiwizard",
"clobbers": [
"window.WifiWizard"
]
},
{
"id": "cordova-plugin-certificates.Certificates",
"file": "plugins/cordova-plugin-certificates/www/certificate.js",
"pluginId": "cordova-plugin-certificates",
"clobbers": [
"cordova.plugins.certificates"
]
}
],
"plugin_metadata": {
"com.jiliac.systemvolume": "0.1.0",
"com.phonegap.plugins.OrientationLock": "0.1",
"cordova-plugin-tts": "0.2.3",
"cordova.custom.plugins.exitapp": "1.0.0",
"cordova-plugin-compat": "1.0.0",
"cordova-plugin-geolocation": "2.4.3",
"cordova-plugin-battery-status": "1.2.4",
"cordova-plugin-x-toast": "2.6.0",
"cordova-plugin-app-version": "0.1.9",
"phonegap-plugin-speech-recognition": "0.2.0",
"cordova-plugin-network-information": "1.3.3",
"cordova-plugin-insomnia": "4.3.0",
"cordova-plugin-whitelist": "1.3.2",
"cordova-plugin-fullscreen": "1.1.0",
"cordova-plugin-file": "4.3.3",
"com.pylonproducts.wifiwizard": "0.2.11",
"cordova-plugin-certificates": "0.6.4"
}
}

View File

@@ -0,0 +1,313 @@
/*
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()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
allprojects {
repositories {
mavenCentral();
jcenter()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.14.1'
}
// 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
}
// .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
// 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()
}
if (ext.cdvBuildToolsVersion == null) {
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
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.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
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('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 {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
defaultConfig {
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
applicationId privateHelpers.extractStringFromManifest("package")
if (cdvMinSdkVersion != null) {
minSdkVersion cdvMinSdkVersion
}
}
lintOptions {
abortOnError false;
}
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
if (Boolean.valueOf(cdvBuildMultipleApks)) {
productFlavors {
armv7 {
versionCode defaultConfig.versionCode*10 + 2
ndk {
abiFilters "armeabi-v7a", ""
}
}
x86 {
versionCode defaultConfig.versionCode*10 + 4
ndk {
abiFilters "x86", ""
}
}
all {
ndk {
abiFilters "all", ""
}
}
}
}
/*
ELSE NOTHING! DON'T MESS WITH THE VERSION CODE IF YOU DON'T HAVE TO!
else if (!cdvVersionCode) {
def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
// Vary versionCode by the two most common API levels:
// 14 is ICS, which is the lowest API level for many apps.
// 20 is Lollipop, which is the lowest API level for the updatable system webview.
if (minSdkVersion >= 20) {
defaultConfig.versionCode += 9
} else if (minSdkVersion >= 14) {
defaultConfig.versionCode += 8
}
}
*/
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
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)
}
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
debugCompile(project(path: "CordovaLib", configuration: "debug"))
releaseCompile(project(path: "CordovaLib", configuration: "release"))
// 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 (task.name == 'validateReleaseSigning' || task.name == 'validateSigningRelease') {
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,10 @@
{
"node": true
, "bitwise": true
, "undef": true
, "trailing": true
, "quotmark": true
, "indent": 4
, "unused": "vars"
, "latedef": "nofunc"
}

415
platforms/android/cordova/Api.js vendored Normal file
View File

@@ -0,0 +1,415 @@
/**
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 path = require('path');
var Q = require('q');
var AndroidProject = require('./lib/AndroidProject');
var AndroidStudio = require('./lib/AndroidStudio');
var PluginManager = require('cordova-common').PluginManager;
var CordovaLogger = require('cordova-common').CordovaLogger;
var selfEvents = require('cordova-common').events;
var PLATFORM = 'android';
function setupEvents(externalEventEmitter) {
if (externalEventEmitter) {
// This will make the platform internal events visible outside
selfEvents.forwardEventsTo(externalEventEmitter);
return externalEventEmitter;
}
// There is no logger if external emitter is not present,
// so attach a console logger
CordovaLogger.get().subscribe(selfEvents);
return selfEvents;
}
/**
* Class, that acts as abstraction over particular platform. Encapsulates the
* platform's properties and methods.
*
* Platform that implements own PlatformApi instance _should implement all
* prototype methods_ of this class to be fully compatible with cordova-lib.
*
* The PlatformApi instance also should define the following field:
*
* * platform: String that defines a platform name.
*/
function Api(platform, platformRootDir, events) {
this.platform = PLATFORM;
this.root = path.resolve(__dirname, '..');
setupEvents(events);
var self = this;
this.locations = {
root: self.root,
www: path.join(self.root, 'assets/www'),
res: path.join(self.root, 'res'),
platformWww: path.join(self.root, 'platform_www'),
configXml: path.join(self.root, 'res/xml/config.xml'),
defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
strings: path.join(self.root, 'res/values/strings.xml'),
manifest: path.join(self.root, 'AndroidManifest.xml'),
build: path.join(self.root, 'build'),
// NOTE: Due to platformApi spec we need to return relative paths here
cordovaJs: 'bin/templates/project/assets/www/cordova.js',
cordovaJsSrc: 'cordova-js-src'
};
// XXX Override some locations for Android Studio projects
if(AndroidStudio.isAndroidStudioProject(self.root) === true) {
selfEvents.emit('log', 'Android Studio project detected');
this.android_studio = true;
this.locations.configXml = path.join(self.root, 'app/src/main/res/xml/config.xml');
this.locations.strings = path.join(self.root, 'app/src/main/res/xml/strings.xml');
this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml');
this.locations.www = path.join(self.root, 'app/src/main/assets/www');
this.locations.res = path.join(self.root, 'app/src/main/res');
}
}
/**
* Installs platform to specified directory and creates a platform project.
*
* @param {String} destination Destination directory, where insatll platform to
* @param {ConfigParser} [config] ConfgiParser instance, used to retrieve
* project creation options, such as package id and project name.
* @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to
* console
*
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError.
*/
Api.createPlatform = function (destination, config, options, events) {
events = setupEvents(events);
var result;
try {
result = require('../../lib/create')
.create(destination, config, options, events)
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi(PLATFORM, destination, events);
});
}
catch (e) {
events.emit('error','createPlatform is not callable from the android project API.');
throw(e);
}
return result;
};
/**
* Updates already installed platform.
*
* @param {String} destination Destination directory, where platform installed
* @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to
* console
*
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError.
*/
Api.updatePlatform = function (destination, options, events) {
events = setupEvents(events);
var result;
try {
result = require('../../lib/create')
.update(destination, options, events)
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi('android', destination, events);
});
}
catch (e) {
events.emit('error','updatePlatform is not callable from the android project API, you will need to do this manually.');
throw(e);
}
return result;
};
/**
* Gets a CordovaPlatform object, that represents the platform structure.
*
* @return {CordovaPlatform} A structure that contains the description of
* platform's file structure and other properties of platform.
*/
Api.prototype.getPlatformInfo = function () {
var result = {};
result.locations = this.locations;
result.root = this.root;
result.name = this.platform;
result.version = require('./version');
result.projectConfig = this._config;
return result;
};
/**
* Updates installed platform with provided www assets and new app
* configuration. This method is required for CLI workflow and will be called
* each time before build, so the changes, made to app configuration and www
* code, will be applied to platform.
*
* @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
* project structure and configuration, that should be applied to platform
* (contains project's www location and ConfigParser instance for project's
* config).
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.prepare = function (cordovaProject, prepareOptions) {
return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions);
};
/**
* Installs a new plugin into platform. This method only copies non-www files
* (sources, libs, etc.) to platform. It also doesn't resolves the
* dependencies of plugin. Both of handling of www files, such as assets and
* js-files and resolving dependencies are the responsibility of caller.
*
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed.
* @param {Object} installOptions An options object. Possible options below:
* @param {Boolean} installOptions.link: Flag that specifies that plugin
* sources will be symlinked to app's directory instead of copying (if
* possible).
* @param {Object} installOptions.variables An object that represents
* variables that will be used to install plugin. See more details on plugin
* variables in documentation:
* https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.addPlugin = function (plugin, installOptions) {
var project = AndroidProject.getProjectFile(this.root);
var self = this;
installOptions = installOptions || {};
installOptions.variables = installOptions.variables || {};
// Add PACKAGE_NAME variable into vars
if (!installOptions.variables.PACKAGE_NAME) {
installOptions.variables.PACKAGE_NAME = project.getPackageName();
}
if(this.android_studio === true) {
installOptions.android_studio = true;
}
return Q()
.then(function () {
//CB-11964: Do a clean when installing the plugin code to get around
//the Gradle bug introduced by the Android Gradle Plugin Version 2.2
//TODO: Delete when the next version of Android Gradle plugin comes out
// Since clean doesn't just clean the build, it also wipes out www, we need
// to pass additional options.
// Do some basic argument parsing
var opts = {};
// Skip cleaning prepared files when not invoking via cordova CLI.
opts.noPrepare = true;
if(!AndroidStudio.isAndroidStudioProject(self.root) && !project.isClean()) {
return self.clean(opts);
}
})
.then(function () {
return PluginManager.get(self.platform, self.locations, project)
.addPlugin(plugin, installOptions);
})
.then(function () {
if (plugin.getFrameworks(this.platform).length === 0) return;
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
}.bind(this))
// CB-11022 Return truthy value to prevent running prepare after
.thenResolve(true);
};
/**
* Removes an installed plugin from platform.
*
* Since method accepts PluginInfo instance as input parameter instead of plugin
* id, caller shoud take care of managing/storing PluginInfo instances for
* future uninstalls.
*
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed.
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.removePlugin = function (plugin, uninstallOptions) {
var project = AndroidProject.getProjectFile(this.root);
if(uninstallOptions && uninstallOptions.usePlatformWww === true && this.android_studio === true) {
uninstallOptions.usePlatformWww = false;
uninstallOptions.android_studio = true;
}
return PluginManager.get(this.platform, this.locations, project)
.removePlugin(plugin, uninstallOptions)
.then(function () {
if (plugin.getFrameworks(this.platform).length === 0) return;
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
}.bind(this))
// CB-11022 Return truthy value to prevent running prepare after
.thenResolve(true);
};
/**
* Builds an application package for current platform.
*
* @param {Object} buildOptions A build options. This object's structure is
* highly depends on platform's specific. The most common options are:
* @param {Boolean} buildOptions.debug Indicates that packages should be
* built with debug configuration. This is set to true by default unless the
* 'release' option is not specified.
* @param {Boolean} buildOptions.release Indicates that packages should be
* built with release configuration. If not set to true, debug configuration
* will be used.
* @param {Boolean} buildOptions.device Specifies that built app is intended
* to run on device
* @param {Boolean} buildOptions.emulator: Specifies that built app is
* intended to run on emulator
* @param {String} buildOptions.target Specifies the device id that will be
* used to run built application.
* @param {Boolean} buildOptions.nobuild Indicates that this should be a
* dry-run call, so no build artifacts will be produced.
* @param {String[]} buildOptions.archs Specifies chip architectures which
* app packages should be built for. List of valid architectures is depends on
* platform.
* @param {String} buildOptions.buildConfig The path to build configuration
* file. The format of this file is depends on platform.
* @param {String[]} buildOptions.argv Raw array of command-line arguments,
* passed to `build` command. The purpose of this property is to pass a
* platform-specific arguments, and eventually let platform define own
* arguments processing logic.
*
* @return {Promise<Object[]>} A promise either fulfilled with an array of build
* artifacts (application packages) if package was built successfully,
* or rejected with CordovaError. The resultant build artifact objects is not
* strictly typed and may conatin arbitrary set of fields as in sample below.
*
* {
* architecture: 'x86',
* buildType: 'debug',
* path: '/path/to/build',
* type: 'app'
* }
*
* The return value in most cases will contain only one item but in some cases
* there could be multiple items in output array, e.g. when multiple
* arhcitectures is specified.
*/
Api.prototype.build = function (buildOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/build').run.call(self, buildOptions);
})
.then(function (buildResults) {
// Cast build result to array of build artifacts
return buildResults.apkPaths.map(function (apkPath) {
return {
buildType: buildResults.buildType,
buildMethod: buildResults.buildMethod,
path: apkPath,
type: 'apk'
};
});
});
};
/**
* Builds an application package for current platform and runs it on
* specified/default device. If no 'device'/'emulator'/'target' options are
* specified, then tries to run app on default device if connected, otherwise
* runs the app on emulator.
*
* @param {Object} runOptions An options object. The structure is the same
* as for build options.
*
* @return {Promise} A promise either fulfilled if package was built and ran
* successfully, or rejected with CordovaError.
*/
Api.prototype.run = function(runOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/run').run.call(self, runOptions);
});
};
/**
* Cleans out the build artifacts from platform's directory, and also
* cleans out the platform www directory if called without options specified.
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError.
*/
Api.prototype.clean = function(cleanOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/build').runClean.call(self, cleanOptions);
})
.then(function () {
return require('./lib/prepare').clean.call(self, cleanOptions);
});
};
/**
* Performs a requirements check for current platform. Each platform defines its
* own set of requirements, which should be resolved before platform can be
* built successfully.
*
* @return {Promise<Requirement[]>} Promise, resolved with set of Requirement
* objects for current platform.
*/
Api.prototype.requirements = function() {
return require('./lib/check_reqs').check_all();
};
module.exports = Api;

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
/*
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 android_sdk = require('./lib/android_sdk');
android_sdk.print_newest_available_sdk_target().done(null, function(err) {
console.error(err);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0android_sdk_version"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'android_sdk_version' script in 'bin' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env node
/*
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 args = process.argv;
var Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
require('./lib/build').help();
// Do some basic argument parsing
var buildOpts = nopt({
'verbose' : Boolean,
'silent' : Boolean,
'debug' : Boolean,
'release' : Boolean,
'nobuild': Boolean,
'buildConfig' : path
}, { 'd' : '--verbose' });
// Make buildOptions compatible with PlatformApi build method spec
buildOpts.argv = buildOpts.argv.original;
require('./loggingHelper').adjustLoggerLevel(buildOpts);
new Api().build(buildOpts)
.catch(function(err) {
console.error(err.stack);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0build"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'build' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env node
/*
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 check_reqs = require('./lib/check_reqs');
check_reqs.run().done(
function success() {
console.log('Looks like your environment fully supports cordova-android development!');
}, function fail(err) {
console.log(err);
process.exit(2);
}
);

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0check_reqs"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'check_reqs' script in 'bin' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env node
/*
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 Api = require('./Api');
var path = require('path');
var nopt = require('nopt');
// Support basic help commands
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
console.log('Cleans the project directory.');
process.exit(0);
}
// Do some basic argument parsing
var opts = nopt({
'verbose' : Boolean,
'silent' : Boolean
}, { 'd' : '--verbose' });
// Make buildOptions compatible with PlatformApi clean method spec
opts.argv = opts.argv.original;
// Skip cleaning prepared files when not invoking via cordova CLI.
opts.noPrepare = true;
require('./loggingHelper').adjustLoggerLevel(opts);
new Api().clean(opts)
.catch(function(err) {
console.error(err.stack);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0clean"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'clean' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,26 @@
<?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.
-->
<widget xmlns = "http://www.w3.org/ns/widgets"
id = "io.cordova.helloCordova"
version = "2.0.0">
<!-- Preferences for Android -->
<preference name="loglevel" value="DEBUG" />
</widget>

105
platforms/android/cordova/lib/Adb.js vendored Normal file
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.
*/
var Q = require('q');
var os = require('os');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var Adb = {};
function isDevice(line) {
return line.match(/\w+\tdevice/) && !line.match(/emulator/);
}
function isEmulator(line) {
return line.match(/device/) && line.match(/emulator/);
}
/**
* Lists available/connected devices and emulators
*
* @param {Object} opts Various options
* @param {Boolean} opts.emulators Specifies whether this method returns
* emulators only
*
* @return {Promise<String[]>} list of available/connected
* devices/emulators
*/
Adb.devices = function (opts) {
return spawn('adb', ['devices'], {cwd: os.tmpdir()})
.then(function(output) {
return output.split('\n').filter(function (line) {
// Filter out either real devices or emulators, depending on options
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
}).map(function (line) {
return line.replace(/\tdevice/, '').replace('\r', '');
});
});
};
Adb.install = function (target, packagePath, opts) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r');
return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()})
.then(function(output) {
// 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure
if (output.match(/Failure/)) {
if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.';
} else if (output.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
output += '\n\n' + 'You\'re trying to install apk with a lower versionCode that is already installed.' +
'\nEither uninstall an app or increment the versionCode.';
}
return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
}
});
};
Adb.uninstall = function (target, packageId) {
events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...');
return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
};
Adb.shell = function (target, shellCommand) {
events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
var args = ['-s', target, 'shell'];
shellCommand = shellCommand.split(/\s+/);
return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()})
.catch(function (output) {
return Q.reject(new CordovaError('Failed to execute shell command "' +
shellCommand + '"" on device: ' + output));
});
};
Adb.start = function (target, activityName) {
events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName)
.catch(function (output) {
return Q.reject(new CordovaError('Failed to start application "' +
activityName + '"" on device: ' + output));
});
};
module.exports = Adb;

View File

@@ -0,0 +1,161 @@
/**
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 fs = require('fs');
var et = require('elementtree');
var xml= require('cordova-common').xmlHelpers;
var DEFAULT_ORIENTATION = 'default';
/** Wraps an AndroidManifest file */
function AndroidManifest(path) {
this.path = path;
this.doc = xml.parseElementtreeSync(path);
if (this.doc.getroot().tag !== 'manifest') {
throw new Error('AndroidManifest at ' + path + ' has incorrect root node name (expected "manifest")');
}
}
AndroidManifest.prototype.getVersionName = function() {
return this.doc.getroot().attrib['android:versionName'];
};
AndroidManifest.prototype.setVersionName = function(versionName) {
this.doc.getroot().attrib['android:versionName'] = versionName;
return this;
};
AndroidManifest.prototype.getVersionCode = function() {
return this.doc.getroot().attrib['android:versionCode'];
};
AndroidManifest.prototype.setVersionCode = function(versionCode) {
this.doc.getroot().attrib['android:versionCode'] = versionCode;
return this;
};
AndroidManifest.prototype.getPackageId = function() {
/*jshint -W069 */
return this.doc.getroot().attrib['package'];
/*jshint +W069 */
};
AndroidManifest.prototype.setPackageId = function(pkgId) {
/*jshint -W069 */
this.doc.getroot().attrib['package'] = pkgId;
/*jshint +W069 */
return this;
};
AndroidManifest.prototype.getActivity = function() {
var activity = this.doc.getroot().find('./application/activity');
return {
getName: function () {
return activity.attrib['android:name'];
},
setName: function (name) {
if (!name) {
delete activity.attrib['android:name'];
} else {
activity.attrib['android:name'] = name;
}
return this;
},
getOrientation: function () {
return activity.attrib['android:screenOrientation'];
},
setOrientation: function (orientation) {
if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
delete activity.attrib['android:screenOrientation'];
} else {
activity.attrib['android:screenOrientation'] = orientation;
}
return this;
},
getLaunchMode: function () {
return activity.attrib['android:launchMode'];
},
setLaunchMode: function (launchMode) {
if (!launchMode) {
delete activity.attrib['android:launchMode'];
} else {
activity.attrib['android:launchMode'] = launchMode;
}
return this;
}
};
};
['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion']
.forEach(function(sdkPrefName) {
// Copy variable reference to avoid closure issues
var prefName = sdkPrefName;
AndroidManifest.prototype['get' + capitalize(prefName)] = function() {
var usesSdk = this.doc.getroot().find('./uses-sdk');
return usesSdk && usesSdk.attrib['android:' + prefName];
};
AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) {
var usesSdk = this.doc.getroot().find('./uses-sdk');
if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
usesSdk = new et.Element('uses-sdk');
this.doc.getroot().append(usesSdk);
}
if (prefValue) {
usesSdk.attrib['android:' + prefName] = prefValue;
}
return this;
};
});
AndroidManifest.prototype.getDebuggable = function() {
return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
};
AndroidManifest.prototype.setDebuggable = function(value) {
var application = this.doc.getroot().find('./application');
if (value) {
application.attrib['android:debuggable'] = 'true';
} else {
// The default value is "false", so we can remove attribute at all.
delete application.attrib['android:debuggable'];
}
return this;
};
/**
* Writes manifest to disk syncronously. If filename is specified, then manifest
* will be written to that file
*
* @param {String} [destPath] File to write manifest to. If omitted,
* manifest will be written to file it has been read from.
*/
AndroidManifest.prototype.write = function(destPath) {
fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
};
module.exports = AndroidManifest;
function capitalize (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@@ -0,0 +1,210 @@
/**
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 fs = require('fs');
var path = require('path');
var properties_parser = require('properties-parser');
var AndroidManifest = require('./AndroidManifest');
var AndroidStudio = require('./AndroidStudio');
var pluginHandlers = require('./pluginHandlers');
var projectFileCache = {};
function addToPropertyList(projectProperties, key, value) {
var i = 1;
while (projectProperties.get(key + '.' + i))
i++;
projectProperties.set(key + '.' + i, value);
projectProperties.dirty = true;
}
function removeFromPropertyList(projectProperties, key, value) {
var i = 1;
var currentValue;
while ((currentValue = projectProperties.get(key + '.' + i))) {
if (currentValue === value) {
while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
projectProperties.set(key + '.' + i, currentValue);
i++;
}
projectProperties.set(key + '.' + i);
break;
}
i++;
}
projectProperties.dirty = true;
}
function getRelativeLibraryPath (parentDir, subDir) {
var libraryPath = path.relative(parentDir, subDir);
return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
}
function AndroidProject(projectDir) {
this._propertiesEditors = {};
this._subProjectDirs = {};
this._dirty = false;
this.projectDir = projectDir;
this.platformWww = path.join(this.projectDir, 'platform_www');
this.www = path.join(this.projectDir, 'assets/www');
if(AndroidStudio.isAndroidStudioProject(projectDir) === true) {
this.www = path.join(this.projectDir, 'app/src/main/assets/www');
}
}
AndroidProject.getProjectFile = function (projectDir) {
if (!projectFileCache[projectDir]) {
projectFileCache[projectDir] = new AndroidProject(projectDir);
}
return projectFileCache[projectDir];
};
AndroidProject.purgeCache = function (projectDir) {
if (projectDir) {
delete projectFileCache[projectDir];
} else {
projectFileCache = {};
}
};
/**
* Reads the package name out of the Android Manifest file
*
* @param {String} projectDir The absolute path to the directory containing the project
*
* @return {String} The name of the package
*/
AndroidProject.prototype.getPackageName = function() {
var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml');
if(AndroidStudio.isAndroidStudioProject(this.projectDir) === true) {
manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
}
return new AndroidManifest(manifestPath).getPackageId();
};
AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) {
// All custom subprojects are prefixed with the last portion of the package id.
// This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
var packageName = this.getPackageName();
var lastDotIndex = packageName.lastIndexOf('.');
var prefix = packageName.substring(lastDotIndex + 1);
var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
return subRelativeDir;
};
AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var subProjectFile = path.resolve(subDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
// TODO: Setting the target needs to happen only for pre-3.7.0 projects
if (fs.existsSync(subProjectFile)) {
var subProperties = this._getPropertiesFile(subProjectFile);
subProperties.set('target', parentProperties.get('target'));
subProperties.dirty = true;
this._subProjectDirs[subDir] = true;
}
addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
delete this._subProjectDirs[subDir];
this._dirty = true;
};
AndroidProject.prototype.addGradleReference = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.addSystemLibrary = function(parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true;
};
AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true;
};
AndroidProject.prototype.write = function() {
if (!this._dirty) {
return;
}
this._dirty = false;
for (var filename in this._propertiesEditors) {
var editor = this._propertiesEditors[filename];
if (editor.dirty) {
fs.writeFileSync(filename, editor.toString());
editor.dirty = false;
}
}
};
AndroidProject.prototype._getPropertiesFile = function (filename) {
if (!this._propertiesEditors[filename]) {
if (fs.existsSync(filename)) {
this._propertiesEditors[filename] = properties_parser.createEditor(filename);
} else {
this._propertiesEditors[filename] = properties_parser.createEditor();
}
}
return this._propertiesEditors[filename];
};
AndroidProject.prototype.getInstaller = function (type) {
return pluginHandlers.getInstaller(type);
};
AndroidProject.prototype.getUninstaller = function (type) {
return pluginHandlers.getUninstaller(type);
};
/*
* This checks if an Android project is clean or has old build artifacts
*/
AndroidProject.prototype.isClean = function() {
var build_path = path.join(this.projectDir, 'build');
//If the build directory doesn't exist, it's clean
return !(fs.existsSync(build_path));
};
module.exports = AndroidProject;

View File

@@ -0,0 +1,42 @@
/*
* This is a simple routine that checks if project is an Android Studio Project
*
* @param {String} root Root folder of the project
*/
/*jshint esnext: false */
var path = require('path');
var fs = require('fs');
var CordovaError = require('cordova-common').CordovaError;
module.exports.isAndroidStudioProject = function isAndroidStudioProject(root) {
var eclipseFiles = ['AndroidManifest.xml', 'libs', 'res', 'project.properties', 'platform_www'];
var androidStudioFiles = ['app', 'gradle', 'app/src/main/res'];
// assume it is an AS project and not an Eclipse project
var isEclipse = false;
var isAS = true;
if(!fs.existsSync(root)) {
throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root);
}
// if any of the following exists, then we are not an ASProj
eclipseFiles.forEach(function(file) {
if(fs.existsSync(path.join(root, file))) {
isEclipse = true;
}
});
// if it is NOT an eclipse project, check that all required files exist
if(!isEclipse) {
androidStudioFiles.forEach(function(file){
if(!fs.existsSync(path.join(root, file))) {
console.log('missing file :: ' + file);
isAS = false;
}
});
}
return (!isEclipse && isAS);
};

View File

@@ -0,0 +1,106 @@
/*
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 Q = require('q'),
superspawn = require('cordova-common').superspawn;
var suffix_number_regex = /(\d+)$/;
// Used for sorting Android targets, example strings to sort:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
// The idea is to sort based on largest "suffix" number - meaning the bigger
// the number at the end, the more recent the target, the closer to the
// start of the array.
function sort_by_largest_numerical_suffix(a, b) {
var suffix_a = a.match(suffix_number_regex);
var suffix_b = b.match(suffix_number_regex);
if (suffix_a && suffix_b) {
// If the two targets being compared have suffixes, return less than
// zero, or greater than zero, based on which suffix is larger.
return (parseInt(suffix_a[1]) > parseInt(suffix_b[1]) ? -1 : 1);
} else {
// If no suffix numbers were detected, leave the order as-is between
// elements a and b.
return 0;
}
}
module.exports.print_newest_available_sdk_target = function() {
return module.exports.list_targets()
.then(function(targets) {
targets.sort(sort_by_largest_numerical_suffix);
console.log(targets[0]);
});
};
module.exports.version_string_to_api_level = {
'4.0': 14,
'4.0.3': 15,
'4.1': 16,
'4.2': 17,
'4.3': 18,
'4.4': 19,
'4.4W': 20,
'5.0': 21,
'5.1': 22,
'6.0': 23,
'7.0': 24,
'7.1.1': 25
};
function parse_targets(output) {
var target_out = output.split('\n');
var targets = [];
for (var i = target_out.length - 1; i >= 0; i--) {
if(target_out[i].match(/id:/)) { // if "id:" is in the line...
targets.push(target_out[i].match(/"(.+)"/)[1]); //.. match whatever is in quotes.
}
}
return targets;
}
module.exports.list_targets_with_android = function() {
return superspawn.spawn('android', ['list', 'target'])
.then(parse_targets);
};
module.exports.list_targets_with_avdmanager = function() {
return superspawn.spawn('avdmanager', ['list', 'target'])
.then(parse_targets);
};
module.exports.list_targets = function() {
return module.exports.list_targets_with_avdmanager()
.catch(function(err) {
// If there's an error, like avdmanager could not be found, we can try
// as a last resort, to run `android`, in case this is a super old
// SDK installation.
if (err && (err.code == 'ENOENT' || (err.stderr && err.stderr.match(/not recognized/)))) {
return module.exports.list_targets_with_android();
} else throw err;
})
.then(function(targets) {
if (targets.length === 0) {
return Q.reject(new Error('No android targets (SDKs) installed!'));
}
return targets;
});
};

301
platforms/android/cordova/lib/build.js vendored Normal file
View File

@@ -0,0 +1,301 @@
#!/usr/bin/env node
/*
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 Q = require('q'),
path = require('path'),
fs = require('fs'),
nopt = require('nopt');
var Adb = require('./Adb');
var builders = require('./builders/builders');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
function parseOpts(options, resolvedTarget, projectRoot) {
options = options || {};
options.argv = nopt({
gradle: Boolean,
ant: Boolean,
prepenv: Boolean,
versionCode: String,
minSdkVersion: String,
gradleArg: [String, Array],
keystore: path,
alias: String,
storePassword: String,
password: String,
keystoreType: String
}, {}, options.argv, 0);
var ret = {
buildType: options.release ? 'release' : 'debug',
buildMethod: process.env.ANDROID_BUILD || 'gradle',
prepEnv: options.argv.prepenv,
arch: resolvedTarget && resolvedTarget.arch,
extraArgs: []
};
if (options.argv.ant || options.argv.gradle)
ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
if (options.nobuild) ret.buildMethod = 'none';
if (options.argv.versionCode)
ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
if (options.argv.minSdkVersion)
ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
if (options.argv.gradleArg) {
ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
}
var packageArgs = {};
if (options.argv.keystore)
packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
if (options.argv[flagName])
packageArgs[flagName] = options.argv[flagName];
});
var buildConfig = options.buildConfig;
// If some values are not specified as command line arguments - use build config to supplement them.
// Command line arguemnts have precedence over build config.
if (buildConfig) {
if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig);
}
events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig));
var buildjson = fs.readFileSync(buildConfig, 'utf8');
var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
if (config.android && config.android[ret.buildType]) {
var androidInfo = config.android[ret.buildType];
if(androidInfo.keystore && !packageArgs.keystore) {
if(androidInfo.keystore.substr(0,1) === '~') {
androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
}
packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
}
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
packageArgs[key] = packageArgs[key] || androidInfo[key];
});
}
}
if (packageArgs.keystore && packageArgs.alias) {
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
packageArgs.password, packageArgs.keystoreType);
}
if(!ret.packageInfo) {
if(Object.keys(packageArgs).length > 0) {
events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
}
}
return ret;
}
/*
* Builds the project with the specifed options
* Returns a promise.
*/
module.exports.runClean = function(options) {
var opts = parseOpts(options, null, this.root);
var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
return builder.clean(opts);
});
};
/**
* Builds the project with the specifed options.
*
* @param {BuildOptions} options A set of options. See PlatformApi.build
* method documentation for reference.
* @param {Object} optResolvedTarget A deployment target. Used to pass
* target architecture from upstream 'run' call. TODO: remove this option in
* favor of setting buildOptions.archs field.
*
* @return {Promise<Object>} Promise, resolved with built packages
* information.
*/
module.exports.run = function(options, optResolvedTarget) {
var opts = parseOpts(options, optResolvedTarget, this.root);
var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
if (opts.prepEnv) {
events.emit('verbose', 'Build file successfully prepared.');
return;
}
return builder.build(opts)
.then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
buildMethod: opts.buildMethod
};
});
});
};
/*
* Detects the architecture of a device/emulator
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function(target) {
function helper() {
return Adb.shell(target, 'cat /proc/cpuinfo')
.then(function(output) {
return /intel/i.exec(output) ? 'x86' : 'arm';
});
}
// It sometimes happens (at least on OS X), that this command will hang forever.
// To fix it, either unplug & replug device, or restart adb server.
return helper()
.timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
.then(null, function(err) {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb'])
.then(function() {
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac.
events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb'])
.then(function() {
return helper()
.then(null, function() {
return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
});
});
});
}, function() {
// For non-killall OS's.
return Q.reject(err);
});
}
throw err;
});
};
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) {
var apkName = path.basename(p);
if (buildResults.buildType == 'debug') {
return /-debug/.exec(apkName);
}
return !/-debug/.exec(apkName);
});
var archPattern = new RegExp('-' + arch);
var hasArchPattern = /-x86|-arm/;
for (var i = 0; i < paths.length; ++i) {
var apkName = path.basename(paths[i]);
if (hasArchPattern.exec(apkName)) {
if (archPattern.exec(apkName)) {
return paths[i];
}
} else {
return paths[i];
}
}
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
};
function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
this.keystore = {
'name': 'key.store',
'value': keystore
};
this.alias = {
'name': 'key.alias',
'value': alias
};
if (storePassword) {
this.storePassword = {
'name': 'key.store.password',
'value': storePassword
};
}
if (password) {
this.password = {
'name': 'key.alias.password',
'value': password
};
}
if (keystoreType) {
this.keystoreType = {
'name': 'key.store.type',
'value': keystoreType
};
}
}
PackageInfo.prototype = {
toProperties: function() {
var self = this;
var result = '';
Object.keys(self).forEach(function(key) {
result += self[key].name;
result += '=';
result += self[key].value.replace(/\\/g, '\\\\');
result += '\n';
});
return result;
}
};
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)');
console.log(' \'--release\': will build project for release');
console.log(' \'--ant\': will build project with ant');
console.log(' \'--gradle\': will build project with gradle (default)');
console.log(' \'--nobuild\': will skip build process (useful when using run command)');
console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
console.log('');
console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
console.log(' \'--alias=\': Alias for the key store. (Required)');
console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
console.log(' \'--password=\': Password for the key. (Optional - prompted)');
console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
process.exit(0);
};

View File

@@ -0,0 +1,156 @@
/*
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var util = require('util');
var shell = require('shelljs');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
var SIGNING_PROPERTIES = '-signing.properties';
var MARKER = 'YOUR CHANGES WILL BE ERASED!';
var TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n';
var GenericBuilder = require('./GenericBuilder');
function AntBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot);
this.binDirs = {ant: this.binDirs.ant};
}
util.inherits(AntBuilder, GenericBuilder);
AntBuilder.prototype.getArgs = function(cmd, opts) {
var args = [cmd, '-f', path.join(this.root, 'build.xml')];
// custom_rules.xml is required for incremental builds.
if (hasCustomRules(this.root)) {
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
}
if(opts.packageInfo) {
args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES));
}
return args;
};
AntBuilder.prototype.prepEnv = function(opts) {
var self = this;
return check_reqs.check_ant()
.then(function() {
// Copy in build.xml on each build so that:
// A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it.
/*jshint -W069 */
var sdkDir = process.env['ANDROID_HOME'];
/*jshint +W069 */
var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
function writeBuildXml(projectPath) {
var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
}
}
writeBuildXml(self.root);
var propertiesObj = self.readProjectProperties();
var subProjects = propertiesObj.libs;
for (var i = 0; i < subProjects.length; ++i) {
writeBuildXml(path.join(self.root, subProjects[i]));
}
if (propertiesObj.systemLibs.length > 0) {
throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Use gradle instead.');
}
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
var propertiesFilePath = path.join(self.root, propertiesFile);
if (opts.packageInfo) {
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
} else if(isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath);
}
});
};
/*
* Builds the project with ant.
* Returns a promise.
*/
AntBuilder.prototype.build = function(opts) {
// Without our custom_rules.xml, we need to clean before building.
var ret = Q();
if (!hasCustomRules(this.root)) {
// clean will call check_ant() for us.
ret = this.clean(opts);
}
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
return check_reqs.check_ant()
.then(function() {
return spawn('ant', args, {stdio: 'pipe'});
}).progress(function (stdio){
if (stdio.stderr) {
process.stderr.write(stdio.stderr);
} else {
process.stdout.write(stdio.stdout);
}
}).catch(function (error) {
if (error.toString().indexOf('Unable to resolve project target') >= 0) {
return check_reqs.check_android_target(error).then(function() {
// If due to some odd reason - check_android_target succeeds
// we should still fail here.
return Q.reject(error);
});
}
return Q.reject(error);
});
};
AntBuilder.prototype.clean = function(opts) {
var args = this.getArgs('clean', opts);
var self = this;
return check_reqs.check_ant()
.then(function() {
return spawn('ant', args, {stdio: 'inherit'});
})
.then(function () {
shell.rm('-rf', path.join(self.root, 'out'));
['debug', 'release'].forEach(function(config) {
var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){
shell.rm('-f', propertiesFilePath);
}
});
});
};
module.exports = AntBuilder;
function hasCustomRules(projectRoot) {
return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
}
function isAutoGenerated(file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}

View File

@@ -0,0 +1,147 @@
/*
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
function GenericBuilder (projectDir) {
this.root = projectDir || path.resolve(__dirname, '../../..');
this.binDirs = {
ant: path.join(this.root, hasCustomRules(this.root) ? 'ant-build' : 'bin'),
gradle: path.join(this.root, 'build', 'outputs', 'apk')
};
}
function hasCustomRules(projectRoot) {
return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
}
GenericBuilder.prototype.prepEnv = function() {
return Q();
};
GenericBuilder.prototype.build = function() {
events.emit('log', 'Skipping build...');
return Q(null);
};
GenericBuilder.prototype.clean = function() {
return Q();
};
GenericBuilder.prototype.findOutputApks = function(build_type, arch) {
var self = this;
return Object.keys(this.binDirs)
.reduce(function (result, builderName) {
var binDir = self.binDirs[builderName];
return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
}, [])
.sort(apkSorter);
};
GenericBuilder.prototype.readProjectProperties = function () {
function findAllUniq(data, r) {
var s = {};
var m;
while ((m = r.exec(data))) {
s[m[1]] = 1;
}
return Object.keys(s);
}
var data = fs.readFileSync(path.join(this.root, 'project.properties'), 'utf8');
return {
libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
};
};
GenericBuilder.prototype.extractRealProjectNameFromManifest = function () {
var manifestPath = path.join(this.root, 'AndroidManifest.xml');
var manifestData = fs.readFileSync(manifestPath, 'utf8');
var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
if (!m) {
throw new CordovaError('Could not find package name in ' + manifestPath);
}
var packageName=m[1];
var lastDotIndex = packageName.lastIndexOf('.');
return packageName.substring(lastDotIndex + 1);
};
module.exports = GenericBuilder;
function apkSorter(fileA, fileB) {
// De-prioritize unsigned builds
var unsignedRE = /-unsigned/;
if (unsignedRE.exec(fileA)) {
return 1;
} else if (unsignedRE.exec(fileB)) {
return -1;
}
var timeDiff = fs.statSync(fileA).mtime - fs.statSync(fileB).mtime;
return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
}
function findOutputApksHelper(dir, build_type, arch) {
var shellSilent = shell.config.silent;
shell.config.silent = true;
var ret = shell.ls(path.join(dir, '*.apk'))
.filter(function(candidate) {
var apkName = path.basename(candidate);
// Need to choose between release and debug .apk.
if (build_type === 'debug') {
return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
}
if (build_type === 'release') {
return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
}
return true;
})
.sort(apkSorter);
shellSilent = shellSilent;
if (ret.length === 0) {
return ret;
}
// Assume arch-specific build if newest apk has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
// And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function(p) {
/*jshint -W018 */
return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific;
/*jshint +W018 */
});
if (archSpecific && ret.length > 1 && arch) {
ret = ret.filter(function(p) {
return path.basename(p).indexOf('-' + arch) != -1;
});
}
return ret;
}

View File

@@ -0,0 +1,279 @@
/*
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 Q = require('q');
var fs = require('fs');
var util = require('util');
var path = require('path');
var shell = require('shelljs');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
var GenericBuilder = require('./GenericBuilder');
var MARKER = 'YOUR CHANGES WILL BE ERASED!';
var SIGNING_PROPERTIES = '-signing.properties';
var TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n';
function GradleBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot);
this.binDirs = {gradle: this.binDirs.gradle};
}
util.inherits(GradleBuilder, GenericBuilder);
GradleBuilder.prototype.getArgs = function(cmd, opts) {
if (cmd == 'release') {
cmd = 'cdvBuildRelease';
} else if (cmd == 'debug') {
cmd = 'cdvBuildDebug';
}
var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
if (opts.arch) {
args.push('-PcdvBuildArch=' + opts.arch);
}
// 10 seconds -> 6 seconds
args.push('-Dorg.gradle.daemon=true');
// to allow dex in process
args.push('-Dorg.gradle.jvmargs=-Xmx2048m');
// allow NDK to be used - required by Gradle 1.5 plugin
args.push('-Pandroid.useDeprecatedNdk=true');
args.push.apply(args, opts.extraArgs);
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// args.push('-Dorg.gradle.parallel=true');
return args;
};
/*
* This returns a promise
*/
GradleBuilder.prototype.runGradleWrapper = function(gradle_cmd) {
var gradlePath = path.join(this.root, 'gradlew');
var wrapperGradle = path.join(this.root, 'wrapper.gradle');
if(fs.existsSync(gradlePath)) {
//Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
} else {
return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'});
}
};
// Makes the project buildable, minus the gradle wrapper.
GradleBuilder.prototype.prepBuildFiles = function() {
// Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
var propertiesObj = this.readProjectProperties();
var subProjects = propertiesObj.libs;
var checkAndCopy = function(subProject, root) {
var subProjectGradle = path.join(root, subProject, 'build.gradle');
// This is the future-proof way of checking if a file exists
// This must be synchronous to satisfy a Travis test
try {
fs.accessSync(subProjectGradle, fs.F_OK);
} catch (e) {
shell.cp('-f', pluginBuildGradle, subProjectGradle);
}
};
for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib') {
checkAndCopy(subProjects[i], this.root);
}
}
var name = this.extractRealProjectNameFromManifest();
//Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
var settingsGradlePaths = subProjects.map(function(p){
var realDir=p.replace(/[/\\]/g, ':');
var libName=realDir.replace(name+'-','');
var str='include ":'+libName+'"\n';
if(realDir.indexOf(name+'-')!==-1)
str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
return str;
});
// Write the settings.gradle file.
fs.writeFileSync(path.join(this.root, 'settings.gradle'),
'// GENERATED FILE - DO NOT EDIT\n' +
'include ":"\n' + settingsGradlePaths.join(''));
// Update dependencies within build.gradle.
var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
var depsList = '';
var root = this.root;
var insertExclude = function(p) {
var gradlePath = path.join(root, p, 'build.gradle');
var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
if(projectGradleFile.indexOf('CordovaLib') != -1) {
depsList += '{\n exclude module:("CordovaLib")\n }\n';
}
else {
depsList +='\n';
}
};
subProjects.forEach(function(p) {
console.log('Subproject Path: ' + p);
var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))';
insertExclude(p);
depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))';
insertExclude(p);
});
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
var SYSTEM_LIBRARY_MAPPINGS = [
[/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
[/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
];
propertiesObj.systemLibs.forEach(function(p) {
var mavenRef;
// It's already in gradle form if it has two ':'s
if (/:.*:/.exec(p)) {
mavenRef = p;
} else {
for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
var pair = SYSTEM_LIBRARY_MAPPINGS[i];
if (pair[0].exec(p)) {
mavenRef = p.replace(pair[0], pair[1]);
break;
}
}
if (!mavenRef) {
throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
}
}
depsList += ' compile "' + mavenRef + '"\n';
});
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
var includeList = '';
propertiesObj.gradleIncludes.forEach(function(includePath) {
includeList += 'apply from: "' + includePath + '"\n';
});
buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
};
GradleBuilder.prototype.prepEnv = function(opts) {
var self = this;
return check_reqs.check_gradle()
.then(function(gradlePath) {
return self.runGradleWrapper(gradlePath);
}).then(function() {
return self.prepBuildFiles();
}).then(function() {
// We now copy the gradle out of the framework
// This is a dirty patch to get the build working
/*
var wrapperDir = path.join(self.root, 'CordovaLib');
if (process.platform == 'win32') {
shell.rm('-f', path.join(self.root, 'gradlew.bat'));
shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
} else {
shell.rm('-f', path.join(self.root, 'gradlew'));
shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
}
shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
shell.mkdir('-p', path.join(self.root, 'gradle'));
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
*/
// If the gradle distribution URL is set, make sure it points to version we want.
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
// For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/;
/*jshint -W069 */
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip';
/*jshint +W069 */
var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.chmod('u+w', gradleWrapperPropertiesPath);
shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath);
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
var propertiesFilePath = path.join(self.root, propertiesFile);
if (opts.packageInfo) {
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
} else if (isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath);
}
});
};
/*
* Builds the project with gradle.
* Returns a promise.
*/
GradleBuilder.prototype.build = function(opts) {
var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
return spawn(wrapper, args, {stdio: 'pipe'})
.progress(function (stdio){
if (stdio.stderr) {
/*
* Workaround for the issue with Java printing some unwanted information to
* stderr instead of stdout.
* This function suppresses 'Picked up _JAVA_OPTIONS' message from being
* printed to stderr. See https://issues.apache.org/jira/browse/CB-9971 for
* explanation.
*/
var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
if (suppressThisLine) {
return;
}
process.stderr.write(stdio.stderr);
} else {
process.stdout.write(stdio.stdout);
}
}).catch(function (error) {
if (error.toString().indexOf('failed to find target with hash string') >= 0) {
return check_reqs.check_android_target(error).then(function() {
// If due to some odd reason - check_android_target succeeds
// we should still fail here.
return Q.reject(error);
});
}
return Q.reject(error);
});
};
GradleBuilder.prototype.clean = function(opts) {
var builder = this;
var wrapper = path.join(this.root, 'gradlew');
var args = builder.getArgs('clean', opts);
return Q().then(function() {
return spawn(wrapper, args, {stdio: 'inherit'});
})
.then(function () {
shell.rm('-rf', path.join(builder.root, 'out'));
['debug', 'release'].forEach(function(config) {
var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){
shell.rm('-f', propertiesFilePath);
}
});
});
};
module.exports = GradleBuilder;
function isAutoGenerated(file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}

View File

@@ -0,0 +1,47 @@
/*
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 CordovaError = require('cordova-common').CordovaError;
var knownBuilders = {
ant: 'AntBuilder',
gradle: 'GradleBuilder',
none: 'GenericBuilder'
};
/**
* Helper method that instantiates and returns a builder for specified build
* type.
*
* @param {String} builderType Builder name to construct and return. Must
* be one of 'ant', 'gradle' or 'none'
*
* @return {Builder} A builder instance for specified build type.
*/
module.exports.getBuilder = function (builderType, projectRoot) {
if (!knownBuilders[builderType])
throw new CordovaError('Builder ' + builderType + ' is not supported.');
try {
var Builder = require('./' + knownBuilders[builderType]);
return new Builder(projectRoot);
} catch (err) {
throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
}
};

View File

@@ -0,0 +1,432 @@
#!/usr/bin/env node
/*
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.
*/
/* jshint sub:true */
var shelljs = require('shelljs'),
child_process = require('child_process'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
os = require('os'),
REPO_ROOT = path.join(__dirname, '..', '..', '..', '..'),
PROJECT_ROOT = path.join(__dirname, '..', '..');
var CordovaError = require('cordova-common').CordovaError;
var superspawn = require('cordova-common').superspawn;
var android_sdk = require('./android_sdk');
function forgivingWhichSync(cmd) {
try {
return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
}
function tryCommand(cmd, errMsg, catchStderr) {
var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) {
if (err) d.reject(new CordovaError(errMsg));
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example
else d.resolve((catchStderr ? stderr : stdout).trim());
});
return d.promise;
}
module.exports.isWindows = function() {
return (os.platform() == 'win32');
};
module.exports.isDarwin = function() {
return (os.platform() == 'darwin');
};
// Get valid target from framework/project.properties if run from this repo
// Otherwise get target from project.properties file within a generated cordova-android project
module.exports.get_target = function() {
function extractFromFile(filePath) {
var target = shelljs.grep(/\btarget=/, filePath);
if (!target) {
throw new Error('Could not find android target within: ' + filePath);
}
return target.split('=')[1].trim();
}
var repo_file = path.join(REPO_ROOT, 'framework', 'project.properties');
if (fs.existsSync(repo_file)) {
return extractFromFile(repo_file);
}
var project_file = path.join(PROJECT_ROOT, 'project.properties');
if (fs.existsSync(project_file)) {
// if no target found, we're probably in a project and project.properties is in PROJECT_ROOT.
return extractFromFile(project_file);
}
throw new Error('Could not find android target in either ' + repo_file + ' nor ' + project_file);
};
// Returns a promise. Called only by build and clean commands.
module.exports.check_ant = function() {
return superspawn.spawn('ant', ['-version'])
.then(function(output) {
// Parse Ant version from command output
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
}).catch(function(err) {
throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.');
});
};
module.exports.get_gradle_wrapper = function() {
var androidStudioPath;
var i = 0;
var foundStudio = false;
var program_dir;
if (module.exports.isDarwin()) {
program_dir = fs.readdirSync('/Applications');
while (i < program_dir.length && !foundStudio) {
if (program_dir[i].startsWith('Android Studio')) {
//TODO: Check for a specific Android Studio version, make sure it's not Canary
androidStudioPath = path.join('/Applications', program_dir[i], 'Contents', 'gradle');
foundStudio = true;
} else { ++i; }
}
} else if (module.exports.isWindows()) {
var androidPath = path.join(process.env['ProgramFiles'], 'Android') + '/';
if (fs.existsSync(androidPath)) {
program_dir = fs.readdirSync(androidPath);
while (i < program_dir.length && !foundStudio) {
if (program_dir[i].startsWith('Android Studio')) {
foundStudio = true;
androidStudioPath = path.join(process.env['ProgramFiles'], 'Android', program_dir[i], 'gradle');
} else { ++i; }
}
}
}
if (androidStudioPath !== null && fs.existsSync(androidStudioPath)) {
var dirs = fs.readdirSync(androidStudioPath);
if(dirs[0].split('-')[0] == 'gradle') {
return path.join(androidStudioPath, dirs[0], 'bin', 'gradle');
}
} else {
//OK, let's try to check for Gradle!
return forgivingWhichSync('gradle');
}
};
// Returns a promise. Called only by build and clean commands.
module.exports.check_gradle = function() {
var sdkDir = process.env['ANDROID_HOME'];
var d = Q.defer();
if (!sdkDir)
return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
var gradlePath = module.exports.get_gradle_wrapper();
if (gradlePath.length !== 0)
d.resolve(gradlePath);
else
d.reject(new CordovaError('Could not find an installed version of Gradle either in Android Studio,\n' +
'or on your system to install the gradle wrapper. Please include gradle \n' +
'in your path, or install Android Studio'));
return d.promise;
};
// Returns a promise.
module.exports.check_java = function() {
var javacPath = forgivingWhichSync('javac');
var hasJavaHome = !!process.env['JAVA_HOME'];
return Q().then(function() {
if (hasJavaHome) {
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
if (!javacPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin');
}
} else {
if (javacPath) {
// OS X has a command for finding JAVA_HOME.
var find_java = '/usr/libexec/java_home';
var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
if (fs.existsSync(find_java)) {
return superspawn.spawn(find_java)
.then(function(stdout) {
process.env['JAVA_HOME'] = stdout.trim();
}).catch(function(err) {
throw new CordovaError(default_java_error_msg);
});
} else {
// See if we can derive it from javac's location.
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
} else {
throw new CordovaError(default_java_error_msg);
}
}
} else if (module.exports.isWindows()) {
// Try to auto-detect java in the default install paths.
var oldSilent = shelljs.config.silent;
shelljs.config.silent = true;
var firstJdkDir =
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
shelljs.config.silent = oldSilent;
if (firstJdkDir) {
// shelljs always uses / in paths.
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
if (!javacPath) {
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
}
process.env['JAVA_HOME'] = firstJdkDir;
}
}
}
}).then(function() {
var msg =
'Failed to run "javac -version", make sure that you have a JDK installed.\n' +
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
if (process.env['JAVA_HOME']) {
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
}
// We use tryCommand with catchStderr = true, because
// javac writes version info to stderr instead of stdout
return tryCommand('javac -version', msg, true)
.then(function (output) {
//Let's check for at least Java 8, and keep it future proof so we can support Java 10
var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output);
return match && match[1];
});
});
};
// Returns a promise.
module.exports.check_android = function() {
return Q().then(function() {
var androidCmdPath = forgivingWhichSync('android');
var adbInPath = forgivingWhichSync('adb');
var avdmanagerInPath = forgivingWhichSync('avdmanager');
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
function maybeSetAndroidHome(value) {
if (!hasAndroidHome && fs.existsSync(value)) {
hasAndroidHome = true;
process.env['ANDROID_HOME'] = value;
}
}
// First ensure ANDROID_HOME is set
// If we have no hints (nothing in PATH), try a few default locations
if (!hasAndroidHome && !androidCmdPath && !adbInPath && !avdmanagerInPath) {
if (module.exports.isWindows()) {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
// Android Studio pre-1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
// Stand-alone installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
} else if (module.exports.isDarwin()) {
// Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
// Android Studio pre-1.0 installer
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
// Stand-alone zip file that user might think to put under /Applications
maybeSetAndroidHome('/Applications/android-sdk-macosx');
maybeSetAndroidHome('/Applications/android-sdk');
}
if (process.env['HOME']) {
// Stand-alone zip file that user might think to put under their home directory
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
}
}
if (!hasAndroidHome) {
// If we dont have ANDROID_HOME, but we do have some tools on the PATH, try to infer from the tooling PATH.
var parentDir, grandParentDir;
if (androidCmdPath) {
parentDir = path.dirname(androidCmdPath);
grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
maybeSetAndroidHome(grandParentDir);
} else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools directory.');
}
}
if (adbInPath) {
parentDir = path.dirname(adbInPath);
grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'platform-tools') {
maybeSetAndroidHome(grandParentDir);
} else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Detected \'adb\' command at ' + parentDir + ' but no \'platform-tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'platform-tools directory.');
}
}
if (avdmanagerInPath) {
parentDir = path.dirname(avdmanagerInPath);
grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'bin' && path.basename(grandParentDir) == 'tools') {
maybeSetAndroidHome(path.dirname(grandParentDir));
} else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Detected \'avdmanager\' command at ' + parentDir + ' but no \'tools' + path.sep + 'bin\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools' + path.sep + 'bin directory.');
}
}
}
if (!process.env['ANDROID_HOME']) {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
}
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
'\nTry update it manually to point to valid SDK directory.');
}
// Next let's make sure relevant parts of the SDK tooling is in our PATH
if (hasAndroidHome && !androidCmdPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
}
if (hasAndroidHome && !adbInPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
}
if (hasAndroidHome && !avdmanagerInPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools', 'bin');
}
return hasAndroidHome;
});
};
// TODO: is this actually needed?
module.exports.getAbsoluteAndroidCmd = function () {
var cmd = forgivingWhichSync('android');
if (cmd.length === 0) {
cmd = forgivingWhichSync('sdkmanager');
}
if (module.exports.isWindows()) {
return '"' + cmd + '"';
}
return cmd.replace(/(\s)/g, '\\$1');
};
module.exports.check_android_target = function(originalError) {
// valid_target can look like:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
var desired_api_level = module.exports.get_target();
return android_sdk.list_targets()
.then(function(targets) {
if (targets.indexOf(desired_api_level) >= 0) {
return targets;
}
var androidCmd = module.exports.getAbsoluteAndroidCmd();
var msg = 'Please install Android target / API level: "' + desired_api_level + '".\n\n' +
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
'You will require:\n' +
'1. "SDK Platform" for API level ' + desired_api_level + '\n' +
'2. "Android SDK Platform-tools (latest)\n' +
'3. "Android SDK Build-tools" (latest)';
if (originalError) {
msg = originalError + '\n' + msg;
}
throw new CordovaError(msg);
});
};
// Returns a promise.
module.exports.run = function() {
return Q.all([this.check_java(), this.check_android()])
.then(function(values) {
console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
if (!values[0]) {
throw new CordovaError('Requirements check failed for JDK 1.8 or greater');
}
if (!values[1]) {
throw new CordovaError('Requirements check failed for Android SDK');
}
});
};
/**
* Object thar represents one of requirements for current platform.
* @param {String} id The unique identifier for this requirements.
* @param {String} name The name of requirements. Human-readable field.
* @param {String} version The version of requirement installed. In some cases could be an array of strings
* (for example, check_android_target returns an array of android targets installed)
* @param {Boolean} installed Indicates whether the requirement is installed or not
*/
var Requirement = function (id, name, version, installed) {
this.id = id;
this.name = name;
this.installed = installed || false;
this.metadata = {
version: version,
};
};
/**
* Methods that runs all checks one by one and returns a result of checks
* as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method
*
* @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
*/
module.exports.check_all = function() {
var requirements = [
new Requirement('java', 'Java JDK'),
new Requirement('androidSdk', 'Android SDK'),
new Requirement('androidTarget', 'Android target'),
new Requirement('gradle', 'Gradle')
];
var checkFns = [
this.check_java,
this.check_android,
this.check_android_target,
this.check_gradle
];
// Then execute requirement checks one-by-one
return checkFns.reduce(function (promise, checkFn, idx) {
// Update each requirement with results
var requirement = requirements[idx];
return promise.then(checkFn)
.then(function (version) {
requirement.installed = true;
requirement.metadata.version = version;
}, function (err) {
requirement.metadata.reason = err instanceof Error ? err.message : err;
});
}, Q())
.then(function () {
// When chain is completed, return requirements array to upstream API
return requirements;
});
};

120
platforms/android/cordova/lib/device.js vendored Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env node
/*
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 Q = require('q'),
build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var events = require('cordova-common').events;
/**
* Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/
module.exports.list = function(lookHarder) {
return Adb.devices()
.then(function(list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
return spawn('killall', ['adb'])
.then(function() {
events.emit('verbose', 'Restarting adb to see if more devices are detected.');
return Adb.devices();
}, function() {
// For non-killall OS's.
return list;
});
}
return list;
});
};
module.exports.resolveTarget = function(target) {
return this.list(true)
.then(function(device_list) {
if (!device_list || !device_list.length) {
return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
if (device_list.indexOf(target) < 0) {
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
}
return build.detectArchitecture(target)
.then(function(arch) {
return { target: target, arch: arch, isEmulator: false };
});
});
};
/*
* Installs a previously built application on the device
* and launches it.
* Returns a promise.
*/
module.exports.install = function(target, buildResults) {
return Q().then(function() {
if (target && typeof target == 'object') {
return target;
}
return module.exports.resolveTarget(target);
}).then(function(resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
var pkgName = manifest.getPackageId();
var launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
return Adb.install(resolvedTarget.target, apk_path, {replace: true})
.catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
'installed app already signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(resolvedTarget.target, pkgName)
.then(function() {
return Adb.install(resolvedTarget.target, apk_path, {replace: true});
});
})
.then(function() {
//unlock screen
return Adb.shell(resolvedTarget.target, 'input keyevent 82');
}).then(function() {
return Adb.start(resolvedTarget.target, launchName);
}).then(function() {
events.emit('log', 'LAUNCH SUCCESS');
});
});
};

View File

@@ -0,0 +1,532 @@
#!/usr/bin/env node
/*
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.
*/
/* jshint sub:true */
var retry = require('./retry');
var build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events;
var superspawn = require('cordova-common').superspawn;
var CordovaError = require('cordova-common').CordovaError;
var shelljs = require('shelljs');
var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs');
var Q = require('q');
var os = require('os');
var fs = require('fs');
var child_process = require('child_process');
// constants
var ONE_SECOND = 1000; // in milliseconds
var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3;
var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
var EXEC_KILL_SIGNAL = 'SIGKILL';
function forgivingWhichSync(cmd) {
try {
return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
}
module.exports.list_images_using_avdmanager = function () {
return superspawn.spawn('avdmanager', ['list', 'avd'])
.then(function(output) {
var response = output.split('\n');
var emulator_list = [];
for (var i = 1; i < response.length; i++) {
// To return more detailed information use img_obj
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Device:\s/)) {
i++;
img_obj['device'] = response[i].split('Device: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Target:\s/)) {
i++;
if (response[i + 1].match(/ABI:\s/)) {
img_obj['abi'] = response[i + 1].split('ABI: ')[1].replace('\r', '');
}
// This next conditional just aims to match the old output of `android list avd`
// We do so so that we don't have to change the logic when parsing for the
// best emulator target to spawn (see below in `best_image`)
// This allows us to transitionally support both `android` and `avdmanager` binaries,
// depending on what SDK version the user has
if (response[i + 1].match(/Based\son:\s/)) {
img_obj['target'] = response[i + 1].split('Based on:')[1];
if (img_obj['target'].match(/Tag\/ABI:\s/)) {
img_obj['target'] = img_obj['target'].split('Tag/ABI:')[0].replace('\r', '').trim();
if (img_obj['target'].indexOf('(') > -1) {
img_obj['target'] = img_obj['target'].substr(0, img_obj['target'].indexOf('(') - 1).trim();
}
}
var version_string = img_obj['target'].replace(/Android\s+/, '');
var api_level = android_sdk.version_string_to_api_level[version_string];
if (api_level) {
img_obj['target'] += ' (API level ' + api_level + ')';
}
}
}
if (response[i + 1].match(/Skin:\s/)) {
i++;
img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', '');
}
emulator_list.push(img_obj);
}
/* To just return a list of names use this
if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
}*/
}
return emulator_list;
});
};
module.exports.list_images_using_android = function() {
return superspawn.spawn('android', ['list', 'avd'])
.then(function(output) {
var response = output.split('\n');
var emulator_list = [];
for (var i = 1; i < response.length; i++) {
// To return more detailed information use img_obj
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Device:\s/)) {
i++;
img_obj['device'] = response[i].split('Device: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
}
if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) {
i++;
var secondLine = response[i + 1].match(/\(API\slevel\s/) ? response[i + 1] : '';
img_obj['target'] = (response[i] + secondLine).split('Target: ')[1].replace('\r', '');
}
if (response[i + 1].match(/ABI:\s/)) {
i++;
img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', '');
}
if (response[i + 1].match(/Skin:\s/)) {
i++;
img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', '');
}
emulator_list.push(img_obj);
}
/* To just return a list of names use this
if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
}*/
}
return emulator_list;
});
};
/**
* Returns a Promise for a list of emulator images in the form of objects
* {
name : <emulator_name>,
device : <device>,
path : <path_to_emulator_image>,
target : <api_target>,
abi : <cpu>,
skin : <skin>
}
*/
module.exports.list_images = function() {
if (forgivingWhichSync('avdmanager')) {
return module.exports.list_images_using_avdmanager();
} else if (forgivingWhichSync('android')) {
return module.exports.list_images_using_android();
} else {
return Q().then(function() {
throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?');
});
}
};
/**
* Will return the closest avd to the projects target
* or undefined if no avds exist.
* Returns a promise.
*/
module.exports.best_image = function() {
return this.list_images()
.then(function(images) {
// Just return undefined if there is no images
if (images.length === 0) return;
var closest = 9999;
var best = images[0];
var project_target = check_reqs.get_target().replace('android-', '');
for (var i in images) {
var target = images[i].target;
if(target) {
var num = target.split('(API level ')[1].replace(')', '');
if (num == project_target) {
return images[i];
} else if (project_target - num < closest && project_target > num) {
closest = project_target - num;
best = images[i];
}
}
}
return best;
});
};
// Returns a promise.
module.exports.list_started = function() {
return Adb.devices({emulators: true});
};
// Returns a promise.
// TODO: we should remove this, there's a more robust method under android_sdk.js
module.exports.list_targets = function() {
return superspawn.spawn('android', ['list', 'targets'], {cwd: os.tmpdir()})
.then(function(output) {
var target_out = output.split('\n');
var targets = [];
for (var i = target_out.length; i >= 0; i--) {
if(target_out[i].match(/id:/)) {
targets.push(targets[i].split(' ')[1]);
}
}
return targets;
});
};
/*
* Gets unused port for android emulator, between 5554 and 5584
* Returns a promise.
*/
module.exports.get_available_port = function () {
var self = this;
return self.list_started()
.then(function (emulators) {
for (var p = 5584; p >= 5554; p-=2) {
if (emulators.indexOf('emulator-' + p) === -1) {
events.emit('verbose', 'Found available port: ' + p);
return p;
}
}
throw new CordovaError('Could not find an available avd port');
});
};
/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
* If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?).
* If no boot timeout is given or the value is negative it will wait forever for
* the emulator to boot
*
* Returns a promise.
*/
module.exports.start = function(emulator_ID, boot_timeout) {
var self = this;
return Q().then(function() {
if (emulator_ID) return Q(emulator_ID);
return self.best_image()
.then(function(best) {
if (best && best.name) {
events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
return best.name;
}
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
return Q.reject(new CordovaError('No emulator images (avds) found.\n' +
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
'2. Create an AVD by running: ' + androidCmd + ' avd\n' +
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
});
}).then(function(emulatorId) {
return self.get_available_port()
.then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461
var emulator_dir = path.dirname(shelljs.which('emulator'));
var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
child_process
.spawn('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })
.unref();
// wait for emulator to start
events.emit('log', 'Waiting for emulator to start...');
return self.wait_for_emulator(port);
});
}).then(function(emulatorId) {
if (!emulatorId)
return Q.reject(new CordovaError('Failed to start emulator'));
//wait for emulator to boot up
process.stdout.write('Waiting for emulator to boot (this may take a while)...');
return self.wait_for_boot(emulatorId, boot_timeout)
.then(function(success) {
if (success) {
events.emit('log','BOOT COMPLETE');
//unlock screen
return Adb.shell(emulatorId, 'input keyevent 82')
.then(function() {
//return the new emulator id for the started emulators
return emulatorId;
});
} else {
// We timed out waiting for the boot to happen
return null;
}
});
});
};
/*
* Waits for an emulator to boot on a given port.
* Returns this emulator's ID in a promise.
*/
module.exports.wait_for_emulator = function(port) {
var self = this;
return Q().then(function() {
var emulator_id = 'emulator-' + port;
return Adb.shell(emulator_id, 'getprop dev.bootcomplete')
.then(function (output) {
if (output.indexOf('1') >= 0) {
return emulator_id;
}
return self.wait_for_emulator(port);
}, function (error) {
if (error && error.message &&
(error.message.indexOf('not found') > -1) ||
error.message.indexOf('device offline') > -1) {
// emulator not yet started, continue waiting
return self.wait_for_emulator(port);
} else {
// something unexpected has happened
throw error;
}
});
});
};
/*
* Waits for the core android process of the emulator to start. Returns a
* promise that resolves to a boolean indicating success. Not specifying a
* time_remaining or passing a negative value will cause it to wait forever
*/
module.exports.wait_for_boot = function(emulator_id, time_remaining) {
var self = this;
return Adb.shell(emulator_id, 'ps')
.then(function(output) {
if (output.match(/android\.process\.acore/)) {
return true;
} else if (time_remaining === 0) {
return false;
} else {
process.stdout.write('.');
// Check at regular intervals
return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function() {
var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
return self.wait_for_boot(emulator_id, updated_time);
});
}
});
};
/*
* Create avd
* TODO : Enter the stdin input required to complete the creation of an avd.
* Returns a promise.
*/
module.exports.create_image = function(name, target) {
console.log('Creating new avd named ' + name);
if (target) {
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', target])
.then(null, function(error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?');
console.error(error);
});
} else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
// TODO: there's a more robust method for finding targets in android_sdk.js
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]])
.then(function() {
// TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.');
console.error('Ensure you have targets available by running the "android" command');
return Q.reject();
}, function(error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(error);
});
}
};
module.exports.resolveTarget = function(target) {
return this.list_started()
.then(function(emulator_list) {
if (emulator_list.length < 1) {
return Q.reject('No running Android emulators found, please start an emulator before deploying your project.');
}
// default emulator
target = target || emulator_list[0];
if (emulator_list.indexOf(target) < 0) {
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
}
return build.detectArchitecture(target)
.then(function(arch) {
return {target:target, arch:arch, isEmulator:true};
});
});
};
/*
* Installs a previously built application on the emulator and launches it.
* If no target is specified, then it picks one.
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function(givenTarget, buildResults) {
var target;
var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
var pkgName = manifest.getPackageId();
// resolve the target emulator
return Q().then(function () {
if (givenTarget && typeof givenTarget == 'object') {
return givenTarget;
} else {
return module.exports.resolveTarget(givenTarget);
}
// set the resolved target
}).then(function (resolvedTarget) {
target = resolvedTarget;
// install the app
}).then(function () {
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Q.when()
.then(function() {
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
var execOptions = {
cwd: os.tmpdir(),
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL
};
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
events.emit('verbose', 'Installing app on emulator...');
// A special function to call adb install in specific environment w/ specific options.
// Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
// to workaround sporadic emulator hangs
function adbInstallWithOptions(target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
var command = 'adb -s ' + target + ' install -r "' + apk + '"';
return Q.promise(function (resolve, reject) {
child_process.exec(command, opts, function(err, stdout, stderr) {
if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
// adb does not return an error code even if installation fails. Instead it puts a specific
// message to stdout, so we have to use RegExp matching to detect installation failure.
else if (/Failure/.test(stdout)) {
if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
' or sign and deploy the unsigned apk manually using Android tools.';
} else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
'\nEither uninstall an app or increment the versionCode.';
}
reject(new CordovaError('Failed to install apk to emulator: ' + stdout));
} else resolve(stdout);
});
});
}
function installPromise () {
return adbInstallWithOptions(target.target, apk_path, execOptions)
.catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
'currently installed app was signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(target.target, pkgName)
.then(function() {
return adbInstallWithOptions(target.target, apk_path, execOptions);
});
});
}
return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise)
.then(function (output) {
events.emit('log', 'INSTALL SUCCESS');
});
});
// unlock screen
}).then(function () {
events.emit('verbose', 'Unlocking screen...');
return Adb.shell(target.target, 'input keyevent 82');
}).then(function () {
Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure
}).then(function (output) {
events.emit('log', 'LAUNCH SUCCESS');
});
};

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env node
/*
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 device = require('./device'),
args = process.argv;
if(args.length > 2) {
var install_target;
if (args[2].substring(0, 9) == '--target=') {
install_target = args[2].substring(9, args[2].length);
device.install(install_target).done(null, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
} else {
device.install().done(null, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
}

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0install-device"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'install-device' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env node
/*
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 emulator = require('./emulator'),
args = process.argv;
var install_target;
if(args.length > 2) {
if (args[2].substring(0, 9) == '--target=') {
install_target = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
emulator.install(install_target).done(null, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0install-emulator"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'install-emulator' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env node
/*
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 devices = require('./device');
// Usage support for when args are given
require('./check_reqs').check_android().then(function() {
devices.list().done(function(device_list) {
device_list && device_list.forEach(function(dev) {
console.log(dev);
});
}, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0list-devices"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-devices' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env node
/*
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 emulators = require('./emulator');
// Usage support for when args are given
require('./check_reqs').check_android().then(function() {
emulators.list_images().done(function(emulator_list) {
emulator_list && emulator_list.forEach(function(emu) {
console.log(emu.name);
});
}, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0list-emulator-images"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-emulator-images' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env node
/*
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 emulators = require('./emulator');
// Usage support for when args are given
require('./check_reqs').check_android().then(function() {
emulators.list_started().done(function(emulator_list) {
emulator_list && emulator_list.forEach(function(emu) {
console.log(emu);
});
}, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0list-started-emulators"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'list-started-emulators' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

56
platforms/android/cordova/lib/log.js vendored Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env node
/*
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 path = require('path'),
os = require('os'),
Q = require('q'),
child_process = require('child_process'),
ROOT = path.join(__dirname, '..', '..');
/*
* Starts running logcat in the shell.
* Returns a promise.
*/
module.exports.run = function() {
var d = Q.defer();
var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()});
adb.stdout.on('data', function(data) {
var lines = data ? data.toString().split('\n') : [];
var out = lines.filter(function(x) { return x.indexOf('nativeGetEnabledTags') < 0; });
console.log(out.join('\n'));
});
adb.stderr.on('data', console.error);
adb.on('close', function(code) {
if (code > 0) {
d.reject('Failed to run logcat command.');
} else d.resolve();
});
return d.promise;
};
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log')));
console.log('Gives the logcat output on the command line.');
process.exit(0);
};

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.
*/
// GENERATED FILE! DO NOT EDIT!
buildscript {
repositories {
mavenCentral()
jcenter()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
}
apply plugin: 'com.android.library'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
debugCompile project(path: ":CordovaLib", configuration: "debug")
releaseCompile project(path: ":CordovaLib", configuration: "release")
}
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
publishNonDefault true
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}
if (file('build-extras.gradle').exists()) {
apply from: 'build-extras.gradle'
}

View File

@@ -0,0 +1,308 @@
/*
*
* Copyright 2013 Anis Kadri
*
* Licensed 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.
*
*/
/* jshint unused: vars */
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var handlers = {
'source-file':{
install:function(obj, plugin, project, options) {
if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id));
if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id));
var dest = path.join(obj.targetDir, path.basename(obj.src));
if(options && options.android_studio === true) {
dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
}
if (options && options.force) {
copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
} else {
copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
}
},
uninstall:function(obj, plugin, project, options) {
var dest = path.join(obj.targetDir, path.basename(obj.src));
if(options && options.android_studio === true) {
dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
}
deleteJava(project.projectDir, dest);
}
},
'lib-file':{
install:function(obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src));
if(options && options.android_studio === true) {
dest = path.join('app/libs', path.basename(obj.src));
}
copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
},
uninstall:function(obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src));
if(options && options.android_studio === true) {
dest = path.join('app/libs', path.basename(obj.src));
}
removeFile(project.projectDir, dest);
}
},
'resource-file':{
install:function(obj, plugin, project, options) {
copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link));
},
uninstall:function(obj, plugin, project, options) {
removeFile(project.projectDir, path.normalize(obj.target));
}
},
'framework': {
install:function(obj, plugin, project, options) {
var src = obj.src;
if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
events.emit('verbose', 'Installing Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir;
if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, !!(options && options.link));
subDir = path.resolve(project.projectDir, subRelativeDir);
} else {
obj.type = 'sys';
subDir = src;
}
if (obj.type == 'gradleReference') {
project.addGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') {
project.addSystemLibrary(parentDir, subDir);
} else {
project.addSubProject(parentDir, subDir);
}
},
uninstall:function(obj, plugin, project, options) {
var src = obj.src;
if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
events.emit('verbose', 'Uninstalling Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir;
if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
removeFile(project.projectDir, subRelativeDir);
subDir = path.resolve(project.projectDir, subRelativeDir);
// If it's the last framework in the plugin, remove the parent directory.
var parDir = path.dirname(subDir);
if (fs.existsSync(parDir) && fs.readdirSync(parDir).length === 0) {
fs.rmdirSync(parDir);
}
} else {
obj.type = 'sys';
subDir = src;
}
if (obj.type == 'gradleReference') {
project.removeGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') {
project.removeSystemLibrary(parentDir, subDir);
} else {
project.removeSubProject(parentDir, subDir);
}
}
},
asset:{
install:function(obj, plugin, project, options) {
if (!obj.src) {
throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
}
if (!obj.target) {
throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
}
copyFile(plugin.dir, obj.src, project.www, obj.target);
if (options && options.usePlatformWww) {
// CB-11022 copy file to both directories if usePlatformWww is specified
copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
}
},
uninstall:function(obj, plugin, project, options) {
var target = obj.target || obj.src;
if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
removeFileF(path.resolve(project.www, target));
removeFileF(path.resolve(project.www, 'plugins', plugin.id));
if (options && options.usePlatformWww) {
// CB-11022 remove file from both directories if usePlatformWww is specified
removeFileF(path.resolve(project.platformWww, target));
removeFileF(path.resolve(project.platformWww, 'plugins', plugin.id));
}
}
},
'js-module': {
install: function (obj, plugin, project, options) {
// Copy the plugin's files into the www directory.
var moduleSource = path.resolve(plugin.dir, obj.src);
var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
// Read in the file, prepend the cordova.define, and write it back out.
var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
if (moduleSource.match(/.*\.json$/)) {
scriptContent = 'module.exports = ' + scriptContent;
}
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
var wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(wwwDest));
fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
if (options && options.usePlatformWww) {
// CB-11022 copy file to both directories if usePlatformWww is specified
var platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(platformWwwDest));
fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
}
},
uninstall: function (obj, plugin, project, options) {
var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
removeFileAndParents(project.www, pluginRelativePath);
if (options && options.usePlatformWww) {
// CB-11022 remove file from both directories if usePlatformWww is specified
removeFileAndParents(project.platformWww, pluginRelativePath);
}
}
}
};
module.exports.getInstaller = function (type) {
if (handlers[type] && handlers[type].install) {
return handlers[type].install;
}
events.emit('verbose', '<' + type + '> is not supported for android plugins');
};
module.exports.getUninstaller = function(type) {
if (handlers[type] && handlers[type].uninstall) {
return handlers[type].uninstall;
}
events.emit('verbose', '<' + type + '> is not supported for android plugins');
};
function copyFile (plugin_dir, src, project_dir, dest, link) {
src = path.resolve(plugin_dir, src);
if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
// check that src path is inside plugin directory
var real_path = fs.realpathSync(src);
var real_plugin_path = fs.realpathSync(plugin_dir);
if (real_path.indexOf(real_plugin_path) !== 0)
throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
dest = path.resolve(project_dir, dest);
// check that dest path is located in project directory
if (dest.indexOf(project_dir) !== 0)
throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
shell.mkdir('-p', path.dirname(dest));
if (link) {
symlinkFileOrDirTree(src, dest);
} else if (fs.statSync(src).isDirectory()) {
// XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
shell.cp('-Rf', src+'/*', dest);
} else {
shell.cp('-f', src, dest);
}
}
// Same as copy file but throws error if target exists
function copyNewFile (plugin_dir, src, project_dir, dest, link) {
var target_path = path.resolve(project_dir, dest);
if (fs.existsSync(target_path))
throw new CordovaError('"' + target_path + '" already exists!');
copyFile(plugin_dir, src, project_dir, dest, !!link);
}
function symlinkFileOrDirTree(src, dest) {
if (fs.existsSync(dest)) {
shell.rm('-Rf', dest);
}
if (fs.statSync(src).isDirectory()) {
shell.mkdir('-p', dest);
fs.readdirSync(src).forEach(function(entry) {
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
});
}
else {
fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
}
}
// checks if file exists and then deletes. Error if doesn't exist
function removeFile (project_dir, src) {
var file = path.resolve(project_dir, src);
shell.rm('-Rf', file);
}
// deletes file/directory without checking
function removeFileF (file) {
shell.rm('-Rf', file);
}
// Sometimes we want to remove some java, and prune any unnecessary empty directories
function deleteJava (project_dir, destFile) {
removeFileAndParents(project_dir, destFile, 'src');
}
function removeFileAndParents (baseDir, destFile, stopper) {
stopper = stopper || '.';
var file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return;
removeFileF(file);
// check if directory is empty
var curDir = path.dirname(file);
while(curDir !== path.resolve(baseDir, stopper)) {
if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
fs.rmdirSync(curDir);
curDir = path.resolve(curDir, '..');
} else {
// directory not empty...do nothing
break;
}
}
}
function generateAttributeError(attribute, element, id) {
return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
}

471
platforms/android/cordova/lib/prepare.js vendored Normal file
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.
*/
var Q = require('q');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var AndroidManifest = require('./AndroidManifest');
var xmlHelpers = require('cordova-common').xmlHelpers;
var CordovaError = require('cordova-common').CordovaError;
var ConfigParser = require('cordova-common').ConfigParser;
var FileUpdater = require('cordova-common').FileUpdater;
var PlatformJson = require('cordova-common').PlatformJson;
var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
module.exports.prepare = function (cordovaProject, options) {
var self = this;
var platformJson = PlatformJson.load(this.locations.root, this.platform);
var munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider());
this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations);
// Update own www dir with project's www assets and plugins' assets and js-files
return Q.when(updateWww(cordovaProject, this.locations))
.then(function () {
// update project according to config.xml changes.
return updateProjectAccordingTo(self._config, self.locations);
})
.then(function () {
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
})
.then(function () {
events.emit('verbose', 'Prepared android project successfully');
});
};
module.exports.clean = function (options) {
// A cordovaProject isn't passed into the clean() function, because it might have
// been called from the platform shell script rather than the CLI. Check for the
// noPrepare option passed in by the non-CLI clean script. If that's present, or if
// there's no config.xml found at the project root, then don't clean prepared files.
var projectRoot = path.resolve(this.root, '../..');
if ((options && options.noPrepare) || !fs.existsSync(this.locations.configXml) ||
!fs.existsSync(this.locations.configXml)) {
return Q();
}
var projectConfig = new ConfigParser(this.locations.configXml);
var self = this;
return Q().then(function () {
cleanWww(projectRoot, self.locations);
cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
cleanSplashes(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
cleanFileResources(projectRoot, projectConfig, path.relative(projectRoot, self.locations.root));
});
};
/**
* Updates config files in project based on app's config.xml and config munge,
* generated by plugins.
*
* @param {ConfigParser} sourceConfig A project's configuration that will
* be merged into platform's config.xml
* @param {ConfigChanges} configMunger An initialized ConfigChanges instance
* for this platform.
* @param {Object} locations A map of locations for this platform
*
* @return {ConfigParser} An instance of ConfigParser, that
* represents current project's configuration. When returned, the
* configuration is already dumped to appropriate config.xml file.
*/
function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml);
// First cleanup current config and merge project's one into own
// Overwrite platform config.xml with defaults.xml.
shell.cp('-f', locations.defaultConfigXml, locations.configXml);
// Then apply config changes from global munge to all config files
// in project (including project's config)
configMunger.reapply_global_munge().save_all();
events.emit('verbose', 'Merging project\'s config.xml into platform-specific android config.xml');
// Merge changes from app's config.xml into platform's one
var config = new ConfigParser(locations.configXml);
xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
config.doc.getroot(), 'android', /*clobber=*/true);
config.write();
return config;
}
/**
* Logs all file operations via the verbose event stream, indented.
*/
function logFileOp(message) {
events.emit('verbose', ' ' + message);
}
/**
* Updates platform 'www' directory by replacing it with contents of
* 'platform_www' and app www. Also copies project's overrides' folder into
* the platform 'www' folder
*
* @param {Object} cordovaProject An object which describes cordova project.
* @param {Object} destinations An object that contains destination
* paths for www files.
*/
function updateWww(cordovaProject, destinations) {
var sourceDirs = [
path.relative(cordovaProject.root, cordovaProject.locations.www),
path.relative(cordovaProject.root, destinations.platformWww)
];
// If project contains 'merges' for our platform, use them as another overrides
var merges_path = path.join(cordovaProject.root, 'merges', 'android');
if (fs.existsSync(merges_path)) {
events.emit('verbose', 'Found "merges/android" folder. Copying its contents into the android project.');
sourceDirs.push(path.join('merges', 'android'));
}
var targetDir = path.relative(cordovaProject.root, destinations.www);
events.emit(
'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
FileUpdater.mergeAndUpdateDir(
sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
}
/**
* Cleans all files from the platform 'www' directory.
*/
function cleanWww(projectRoot, locations) {
var targetDir = path.relative(projectRoot, locations.www);
events.emit('verbose', 'Cleaning ' + targetDir);
// No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
FileUpdater.mergeAndUpdateDir(
[], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
}
/**
* Updates project structure and AndroidManifest according to project's configuration.
*
* @param {ConfigParser} platformConfig A project's configuration that will
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectAccordingTo(platformConfig, locations) {
// Update app name by editing res/values/strings.xml
var name = platformConfig.name();
var strings = xmlHelpers.parseElementtreeSync(locations.strings);
strings.find('string[@name="app_name"]').text = name.replace(/\'/g, '\\\'');
fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8');
events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings);
// Java packages cannot support dashes
var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
var manifest = new AndroidManifest(locations.manifest);
var orig_pkg = manifest.getPackageId();
manifest.getActivity()
.setOrientation(platformConfig.getPreference('orientation'))
.setLaunchMode(findAndroidLaunchModePreference(platformConfig));
manifest.setVersionName(platformConfig.version())
.setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
.setPackageId(pkg)
.setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android'))
.setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android'))
.setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
.write();
var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java');
var java_files = shell.ls(javaPattern).filter(function(f) {
return shell.grep(/extends\s+CordovaActivity/g, f);
});
if (java_files.length === 0) {
throw new CordovaError('No Java files found that extend CordovaActivity.');
} else if(java_files.length > 1) {
events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]);
}
var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0]));
shell.mkdir('-p', path.dirname(destFile));
shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile);
events.emit('verbose', 'Wrote out Android package name "' + pkg + '" to ' + destFile);
if (orig_pkg !== pkg) {
// If package was name changed we need to remove old java with main activity
shell.rm('-Rf',java_files[0]);
// remove any empty directories
var currentDir = path.dirname(java_files[0]);
var sourcesRoot = path.resolve(locations.root, 'src');
while(currentDir !== sourcesRoot) {
if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
fs.rmdirSync(currentDir);
currentDir = path.resolve(currentDir, '..');
} else {
break;
}
}
}
}
// Consturct the default value for versionCode as
// PATCH + MINOR * 100 + MAJOR * 10000
// see http://developer.android.com/tools/publishing/versioning.html
function default_versionCode(version) {
var nums = version.split('-')[0].split('.');
var versionCode = 0;
if (+nums[0]) {
versionCode += +nums[0] * 10000;
}
if (+nums[1]) {
versionCode += +nums[1] * 100;
}
if (+nums[2]) {
versionCode += +nums[2];
}
events.emit('verbose', 'android-versionCode not found in config.xml. Generating a code based on version in config.xml (' + version + '): ' + versionCode);
return versionCode;
}
function getImageResourcePath(resourcesDir, type, density, name, sourceName) {
if (/\.9\.png$/.test(sourceName)) {
name = name.replace(/\.png$/, '.9.png');
}
var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name);
return resourcePath;
}
function updateSplashes(cordovaProject, platformResourcesDir) {
var resources = cordovaProject.projectConfig.getSplashScreens('android');
// if there are "splash" elements in config.xml
if (resources.length === 0) {
events.emit('verbose', 'This app does not have splash screens defined');
return;
}
var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png');
var hadMdpi = false;
resources.forEach(function (resource) {
if (!resource.density) {
return;
}
if (resource.density == 'mdpi') {
hadMdpi = true;
}
var targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src));
resourceMap[targetPath] = resource.src;
});
// There's no "default" drawable, so assume default == mdpi.
if (!hadMdpi && resources.defaultResource) {
var targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src));
resourceMap[targetPath] = resources.defaultResource.src;
}
events.emit('verbose', 'Updating splash screens at ' + platformResourcesDir);
FileUpdater.updatePaths(
resourceMap, { rootDir: cordovaProject.root }, logFileOp);
}
function cleanSplashes(projectRoot, projectConfig, platformResourcesDir) {
var resources = projectConfig.getSplashScreens('android');
if (resources.length > 0) {
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
// No source paths are specified in the map, so updatePaths() will delete the target files.
FileUpdater.updatePaths(
resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
}
}
function updateIcons(cordovaProject, platformResourcesDir) {
var icons = cordovaProject.projectConfig.getIcons('android');
// if there are icon elements in config.xml
if (icons.length === 0) {
events.emit('verbose', 'This app does not have launcher icons defined');
return;
}
var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'icon.png');
var android_icons = {};
var default_icon;
// http://developer.android.com/design/style/iconography.html
var sizeToDensityMap = {
36: 'ldpi',
48: 'mdpi',
72: 'hdpi',
96: 'xhdpi',
144: 'xxhdpi',
192: 'xxxhdpi'
};
// find the best matching icon for a given density or size
// @output android_icons
var parseIcon = function(icon, icon_size) {
// do I have a platform icon for that density already
var density = icon.density || sizeToDensityMap[icon_size];
if (!density) {
// invalid icon defition ( or unsupported size)
return;
}
var previous = android_icons[density];
if (previous && previous.platform) {
return;
}
android_icons[density] = icon;
};
// iterate over all icon elements to find the default icon and call parseIcon
for (var i=0; i<icons.length; i++) {
var icon = icons[i];
var size = icon.width;
if (!size) {
size = icon.height;
}
if (!size && !icon.density) {
if (default_icon) {
events.emit('verbose', 'Found extra default icon: ' + icon.src + ' (ignoring in favor of ' + default_icon.src + ')');
} else {
default_icon = icon;
}
} else {
parseIcon(icon, size);
}
}
// The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path.
for (var density in android_icons) {
var targetPath = getImageResourcePath(
platformResourcesDir, 'mipmap', density, 'icon.png', path.basename(android_icons[density].src));
resourceMap[targetPath] = android_icons[density].src;
}
// There's no "default" drawable, so assume default == mdpi.
if (default_icon && !android_icons.mdpi) {
var defaultTargetPath = getImageResourcePath(
platformResourcesDir, 'mipmap', 'mdpi', 'icon.png', path.basename(default_icon.src));
resourceMap[defaultTargetPath] = default_icon.src;
}
events.emit('verbose', 'Updating icons at ' + platformResourcesDir);
FileUpdater.updatePaths(
resourceMap, { rootDir: cordovaProject.root }, logFileOp);
}
function cleanIcons(projectRoot, projectConfig, platformResourcesDir) {
var icons = projectConfig.getIcons('android');
if (icons.length > 0) {
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png');
events.emit('verbose', 'Cleaning icons at ' + platformResourcesDir);
// No source paths are specified in the map, so updatePaths() will delete the target files.
FileUpdater.updatePaths(
resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
}
}
/**
* Gets a map containing resources of a specified name from all drawable folders in a directory.
*/
function mapImageResources(rootDir, subDir, type, resourceName) {
var pathMap = {};
shell.ls(path.join(rootDir, subDir, type + '-*'))
.forEach(function (drawableFolder) {
var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
pathMap[imagePath] = null;
});
return pathMap;
}
function updateFileResources(cordovaProject, platformDir) {
var files = cordovaProject.projectConfig.getFileResources('android');
// if there are resource-file elements in config.xml
if (files.length === 0) {
events.emit('verbose', 'This app does not have additional resource files defined');
return;
}
var resourceMap = {};
files.forEach(function(res) {
var targetPath = path.join(platformDir, res.target);
resourceMap[targetPath] = res.src;
});
events.emit('verbose', 'Updating resource files at ' + platformDir);
FileUpdater.updatePaths(
resourceMap, { rootDir: cordovaProject.root }, logFileOp);
}
function cleanFileResources(projectRoot, projectConfig, platformDir) {
var files = projectConfig.getFileResources('android');
if (files.length > 0) {
events.emit('verbose', 'Cleaning resource files at ' + platformDir);
var resourceMap = {};
files.forEach(function(res) {
var filePath = path.join(platformDir, res.target);
resourceMap[filePath] = null;
});
FileUpdater.updatePaths(
resourceMap, { rootDir: projectRoot, all: true}, logFileOp);
}
}
/**
* Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns
* preference value and warns if it doesn't seems to be valid
*
* @param {ConfigParser} platformConfig A configParser instance for
* platform.
*
* @return {String} Preference's value from config.xml or
* default value, if there is no such preference. The default value is
* 'singleTop'
*/
function findAndroidLaunchModePreference(platformConfig) {
var launchMode = platformConfig.getPreference('AndroidLaunchMode');
if (!launchMode) {
// Return a default value
return 'singleTop';
}
var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance'];
var valid = expectedValues.indexOf(launchMode) >= 0;
if (!valid) {
// Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' +
launchMode + '. Expected values are: ' + expectedValues.join(', '));
}
return launchMode;
}

68
platforms/android/cordova/lib/retry.js vendored Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
/*
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.
*/
/* jshint node: true */
'use strict';
var events = require('cordova-common').events;
/*
* Retry a promise-returning function a number of times, propagating its
* results on success or throwing its error on a failed final attempt.
*
* @arg {Number} attemts_left - The number of times to retry the passed call.
* @arg {Function} promiseFunction - A function that returns a promise.
* @arg {...} - Arguments to pass to promiseFunction.
*
* @returns {Promise}
*/
module.exports.retryPromise = function (attemts_left, promiseFunction) {
// NOTE:
// get all trailing arguments, by skipping the first two (attemts_left and
// promiseFunction) because they shouldn't get passed to promiseFunction
var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2);
return promiseFunction.apply(undefined, promiseFunctionArguments).then(
// on success pass results through
function onFulfilled(value) {
return value;
},
// on rejection either retry, or throw the error
function onRejected(error) {
attemts_left -= 1;
if (attemts_left < 1) {
throw error;
}
events.emit('verbose', 'A retried call failed. Retrying ' + attemts_left + ' more time(s).');
// retry call self again with the same arguments, except attemts_left is now lower
var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments);
return module.exports.retryPromise.apply(undefined, fullArguments);
}
);
};

141
platforms/android/cordova/lib/run.js vendored Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
/*
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.
*/
/* jshint loopfunc:true */
var path = require('path'),
build = require('./build'),
emulator = require('./emulator'),
device = require('./device'),
Q = require('q'),
events = require('cordova-common').events;
function getInstallTarget(runOptions) {
var install_target;
if (runOptions.target) {
install_target = runOptions.target;
} else if (runOptions.device) {
install_target = '--device';
} else if (runOptions.emulator) {
install_target = '--emulator';
}
return install_target;
}
/**
* Runs the application on a device if available. If no device is found, it will
* use a started emulator. If no started emulators are found it will attempt
* to start an avd. If no avds are found it will error out.
*
* @param {Object} runOptions various run/build options. See Api.js build/run
* methods for reference.
*
* @return {Promise}
*/
module.exports.run = function(runOptions) {
var self = this;
var install_target = getInstallTarget(runOptions);
return Q()
.then(function() {
if (!install_target) {
// no target given, deploy to device if available, otherwise use the emulator.
return device.list()
.then(function(device_list) {
if (device_list.length > 0) {
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
}
}).then(function() {
if (install_target == '--device') {
return device.resolveTarget(null);
} else if (install_target == '--emulator') {
// Give preference to any already started emulators. Else, start one.
return emulator.list_started()
.then(function(started) {
return started && started.length > 0 ? started[0] : emulator.start();
}).then(function(emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
// They specified a specific device/emulator ID.
return device.list()
.then(function(devices) {
if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target);
}
return emulator.list_started()
.then(function(started_emulators) {
if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target);
}
return emulator.list_images()
.then(function(avds) {
// if target emulator isn't started, then start it.
for (var avd in avds) {
if (avds[avd].name == install_target) {
return emulator.start(install_target)
.then(function(emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
}
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
});
});
});
}).then(function(resolvedTarget) {
// Better just call self.build, but we're doing some processing of
// build results (according to platformApi spec) so they are in different
// format than emulator.install expects.
// TODO: Update emulator/device.install to handle this change
return build.run.call(self, runOptions, resolvedTarget)
.then(function(buildResults) {
if (resolvedTarget.isEmulator) {
return emulator.wait_for_boot(resolvedTarget.target)
.then(function () {
return emulator.install(resolvedTarget, buildResults);
});
}
return device.install(resolvedTarget, buildResults);
});
});
};
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]');
console.log('Build options :');
console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode');
console.log(' --nobuild : Runs the currently built project without recompiling');
console.log('Deploy options :');
console.log(' --device : Will deploy the built project to a device');
console.log(' --emulator : Will deploy the built project to an emulator if one exists');
console.log(' --target=<target_id> : Installs to the target with the specified id.');
process.exit(0);
};

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env node
/*
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 emulator = require('./emulator'),
args = process.argv;
var install_target;
if(args.length > 2) {
if (args[2].substring(0, 9) == '--target=') {
install_target = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}
emulator.start(install_target).done(null, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0start-emulator"
IF EXIST %script_path% (
node "%script_path%" %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'start-emulator' script in 'cordova\lib' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env node
/*
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 log = require('./lib/log'),
reqs = require('./lib/check_reqs'),
args = process.argv;
// Usage support for when args are given
if(args.length > 2) {
log.help();
} else {
reqs.run().done(function() {
return log.run();
}, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
}

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0log"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'log' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,18 @@
var CordovaLogger = require('cordova-common').CordovaLogger;
module.exports = {
adjustLoggerLevel: function (opts) {
if (opts instanceof Array) {
opts.silent = opts.indexOf('--silent') !== -1;
opts.verbose = opts.indexOf('--verbose') !== -1;
}
if (opts.silent) {
CordovaLogger.get().setLevel('error');
}
if (opts.verbose) {
CordovaLogger.get().setLevel('verbose');
}
}
};

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env node
/*
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 Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
require('./lib/run').help();
// Do some basic argument parsing
var runOpts = nopt({
'verbose' : Boolean,
'silent' : Boolean,
'debug' : Boolean,
'release' : Boolean,
'nobuild': Boolean,
'buildConfig' : path,
'archs' : String,
'device' : Boolean,
'emulator': Boolean,
'target' : String
}, { 'd' : '--verbose' });
// Make runOptions compatible with PlatformApi run method spec
runOpts.argv = runOpts.argv.remain;
require('./loggingHelper').adjustLoggerLevel(runOpts);
new Api().run(runOpts)
.catch(function(err) {
console.error(err, err.stack);
process.exit(2);
});

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0run"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'run' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
/*
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.
*/
// Coho updates this line:
var VERSION = "6.2.3";
module.exports.version = VERSION;
if (!module.parent) {
console.log(VERSION);
}

View File

@@ -0,0 +1,26 @@
:: 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.
@ECHO OFF
SET script_path="%~dp0version"
IF EXIST %script_path% (
node %script_path% %*
) ELSE (
ECHO.
ECHO ERROR: Could not find 'version' script in 'cordova' folder, aborting...>&2
EXIT /B 1
)

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", []);
}
};

2208
platforms/android/platform_www/cordova.js vendored Normal file

File diff suppressed because it is too large Load Diff

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