@ -0,0 +1,83 @@ |
|||||
|
<!-- |
||||
|
# |
||||
|
# 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. |
||||
|
# |
||||
|
--> |
||||
|
# Cordova Hooks |
||||
|
|
||||
|
This directory may contain scripts used to customize cordova commands. This |
||||
|
directory used to exist at `.cordova/hooks`, but has now been moved to the |
||||
|
project root. Any scripts you add to these directories will be executed before |
||||
|
and after the commands corresponding to the directory name. Useful for |
||||
|
integrating your own build systems or integrating with version control systems. |
||||
|
|
||||
|
__Remember__: Make your scripts executable. |
||||
|
|
||||
|
## Hook Directories |
||||
|
The following subdirectories will be used for hooks: |
||||
|
|
||||
|
after_build/ |
||||
|
after_compile/ |
||||
|
after_docs/ |
||||
|
after_emulate/ |
||||
|
after_platform_add/ |
||||
|
after_platform_rm/ |
||||
|
after_platform_ls/ |
||||
|
after_plugin_add/ |
||||
|
after_plugin_ls/ |
||||
|
after_plugin_rm/ |
||||
|
after_plugin_search/ |
||||
|
after_prepare/ |
||||
|
after_run/ |
||||
|
after_serve/ |
||||
|
before_build/ |
||||
|
before_compile/ |
||||
|
before_docs/ |
||||
|
before_emulate/ |
||||
|
before_platform_add/ |
||||
|
before_platform_rm/ |
||||
|
before_platform_ls/ |
||||
|
before_plugin_add/ |
||||
|
before_plugin_ls/ |
||||
|
before_plugin_rm/ |
||||
|
before_plugin_search/ |
||||
|
before_prepare/ |
||||
|
before_run/ |
||||
|
before_serve/ |
||||
|
pre_package/ <-- Windows 8 and Windows Phone only. |
||||
|
|
||||
|
## Script Interface |
||||
|
|
||||
|
All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: |
||||
|
|
||||
|
* CORDOVA_VERSION - The version of the Cordova-CLI. |
||||
|
* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). |
||||
|
* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) |
||||
|
* CORDOVA_HOOK - Path to the hook that is being executed. |
||||
|
* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) |
||||
|
|
||||
|
If a script returns a non-zero exit code, then the parent cordova command will be aborted. |
||||
|
|
||||
|
|
||||
|
## Writing hooks |
||||
|
|
||||
|
We highly recommend writting your hooks using Node.js so that they are |
||||
|
cross-platform. Some good examples are shown here: |
||||
|
|
||||
|
[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) |
||||
|
|
@ -0,0 +1,94 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
|
||||
|
// Add Platform Class
|
||||
|
// v1.0
|
||||
|
// Automatically adds the platform class to the body tag
|
||||
|
// after the `prepare` command. By placing the platform CSS classes
|
||||
|
// directly in the HTML built for the platform, it speeds up
|
||||
|
// rendering the correct layout/style for the specific platform
|
||||
|
// instead of waiting for the JS to figure out the correct classes.
|
||||
|
|
||||
|
var fs = require('fs'); |
||||
|
var path = require('path'); |
||||
|
|
||||
|
var rootdir = process.argv[2]; |
||||
|
|
||||
|
function addPlatformBodyTag(indexPath, platform) { |
||||
|
// add the platform class to the body tag
|
||||
|
try { |
||||
|
var platformClass = 'platform-' + platform; |
||||
|
var cordovaClass = 'platform-cordova platform-webview'; |
||||
|
|
||||
|
var html = fs.readFileSync(indexPath, 'utf8'); |
||||
|
|
||||
|
var bodyTag = findBodyTag(html); |
||||
|
if(!bodyTag) return; // no opening body tag, something's wrong
|
||||
|
|
||||
|
if(bodyTag.indexOf(platformClass) > -1) return; // already added
|
||||
|
|
||||
|
var newBodyTag = bodyTag; |
||||
|
|
||||
|
var classAttr = findClassAttr(bodyTag); |
||||
|
if(classAttr) { |
||||
|
// body tag has existing class attribute, add the classname
|
||||
|
var endingQuote = classAttr.substring(classAttr.length-1); |
||||
|
var newClassAttr = classAttr.substring(0, classAttr.length-1); |
||||
|
newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; |
||||
|
newBodyTag = bodyTag.replace(classAttr, newClassAttr); |
||||
|
|
||||
|
} else { |
||||
|
// add class attribute to the body tag
|
||||
|
newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); |
||||
|
} |
||||
|
|
||||
|
html = html.replace(bodyTag, newBodyTag); |
||||
|
|
||||
|
fs.writeFileSync(indexPath, html, 'utf8'); |
||||
|
|
||||
|
process.stdout.write('add to body class: ' + platformClass + '\n'); |
||||
|
} catch(e) { |
||||
|
process.stdout.write(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function findBodyTag(html) { |
||||
|
// get the body tag
|
||||
|
try{ |
||||
|
return html.match(/<body(?=[\s>])(.*?)>/gi)[0]; |
||||
|
}catch(e){} |
||||
|
} |
||||
|
|
||||
|
function findClassAttr(bodyTag) { |
||||
|
// get the body tag's class attribute
|
||||
|
try{ |
||||
|
return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; |
||||
|
}catch(e){} |
||||
|
} |
||||
|
|
||||
|
if (rootdir) { |
||||
|
|
||||
|
// go through each of the platform directories that have been prepared
|
||||
|
var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); |
||||
|
|
||||
|
for(var x=0; x<platforms.length; x++) { |
||||
|
// open up the index.html file at the www root
|
||||
|
try { |
||||
|
var platform = platforms[x].trim().toLowerCase(); |
||||
|
var indexPath; |
||||
|
|
||||
|
if(platform == 'android') { |
||||
|
indexPath = path.join('platforms', platform, 'assets', 'www', 'index.html'); |
||||
|
} else { |
||||
|
indexPath = path.join('platforms', platform, 'www', 'index.html'); |
||||
|
} |
||||
|
|
||||
|
if(fs.existsSync(indexPath)) { |
||||
|
addPlatformBodyTag(indexPath, platform); |
||||
|
} |
||||
|
|
||||
|
} catch(e) { |
||||
|
process.stdout.write(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="utf-8"> |
||||
|
<meta http-equiv="refresh" content="0; url=www/index.html" /> |
||||
|
<title>title</title> |
||||
|
<link rel="stylesheet" href="style.css"> |
||||
|
<script src="script.js"></script> |
||||
|
</head> |
||||
|
<body> |
||||
|
<!-- page content --> |
||||
|
</body> |
||||
|
</html> |
@ -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 |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version='1.0' encoding='utf-8'?> |
||||
|
<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" package="com.ionicframework.app751563" 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="@drawable/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="23" /> |
||||
|
</manifest> |
@ -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> |
@ -0,0 +1,61 @@ |
|||||
|
/* 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. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
|
||||
|
buildscript { |
||||
|
repositories { |
||||
|
mavenCentral() |
||||
|
} |
||||
|
|
||||
|
dependencies { |
||||
|
classpath 'com.android.tools.build:gradle:2.1.0' |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
apply plugin: 'com.android.library' |
||||
|
|
||||
|
ext { |
||||
|
apply from: 'cordova.gradle' |
||||
|
cdvCompileSdkVersion = privateHelpers.getProjectTarget() |
||||
|
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools() |
||||
|
} |
||||
|
|
||||
|
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'] |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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 Integer.parseInt(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) } |
||||
|
} |
||||
|
|
@ -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-23 |
||||
|
apk-configurations= |
||||
|
renderscript.opt.level=O0 |
||||
|
android.library=true |
@ -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; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,144 @@ |
|||||
|
/* |
||||
|
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 android.util.Log; |
||||
|
|
||||
|
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)); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -0,0 +1,72 @@ |
|||||
|
/* |
||||
|
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; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
@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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,508 @@ |
|||||
|
/* |
||||
|
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.util.Log; |
||||
|
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 { |
||||
|
* @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) { |
||||
|
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting"); |
||||
|
LOG.d(TAG, "CordovaActivity.onCreate()"); |
||||
|
|
||||
|
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception |
||||
|
loadConfig(); |
||||
|
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)) { |
||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
||||
|
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")) { |
||||
|
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK); |
||||
|
// Background of activity: |
||||
|
appView.getView().setBackgroundColor(backgroundColor); |
||||
|
} |
||||
|
|
||||
|
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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
@ -0,0 +1,184 @@ |
|||||
|
/* |
||||
|
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; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
|
||||
|
} |
@ -0,0 +1,242 @@ |
|||||
|
/* |
||||
|
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.Log; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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 { |
||||
|
|
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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 = "5.2.2"; |
||||
|
|
||||
|
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); |
||||
|
} |
@ -0,0 +1,81 @@ |
|||||
|
/* |
||||
|
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; |
||||
|
|
||||
|
/** |
||||
|
* 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(); |
||||
|
|
||||
|
/** |
||||
|
* 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); |
||||
|
} |
||||
|
} |
@ -0,0 +1,614 @@ |
|||||
|
/* |
||||
|
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.util.Log; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,360 @@ |
|||||
|
/* |
||||
|
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.util.HashMap; |
||||
|
|
||||
|
/** |
||||
|
* This class exposes methods in Cordova that can be called from JavaScript. |
||||
|
*/ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
@ -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); |
||||
|
} |
@ -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(); |
||||
|
}; |
@ -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); |
||||
|
} |
@ -0,0 +1,234 @@ |
|||||
|
/* |
||||
|
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 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)); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,501 @@ |
|||||
|
/* |
||||
|
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; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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 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(",["); |
||||
|
switch (pluginResult.getMessageType()) { |
||||
|
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()); |
||||
|
} |
||||
|
sb.append("],") |
||||
|
.append(pluginResult.getKeepCallback()) |
||||
|
.append(");"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -0,0 +1,527 @@ |
|||||
|
/* |
||||
|
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; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
} |
@ -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 |
||||
|
} |
||||
|
} |
@ -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)); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
}; |
@ -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); |
||||
|
} |
||||
|
} |
@ -0,0 +1,293 @@ |
|||||
|
/* |
||||
|
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.util.Log; |
||||
|
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(); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -0,0 +1,334 @@ |
|||||
|
/* |
||||
|
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.util.Log; |
||||
|
import android.view.View; |
||||
|
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.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); |
||||
|
} |
||||
|
})); |
||||
|
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue); |
||||
|
exposeJsInterface(webView, bridge); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CordovaWebView getCordovaWebView() { |
||||
|
return parentWebView; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ICordovaCookieManager getCookieManager() { |
||||
|
return cookieManager; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public View getView() { |
||||
|
return webView; |
||||
|
} |
||||
|
|
||||
|
@SuppressLint({"NewApi", "SetJavaScriptEnabled"}) |
||||
|
@SuppressWarnings("deprecation") |
||||
|
private void initWebViewSettings() { |
||||
|
webView.setInitialScale(0); |
||||
|
webView.setVerticalScrollBarEnabled(false); |
||||
|
// Enable JavaScript |
||||
|
final WebSettings settings = webView.getSettings(); |
||||
|
settings.setJavaScriptEnabled(true); |
||||
|
settings.setJavaScriptCanOpenWindowsAutomatically(true); |
||||
|
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); |
||||
|
|
||||
|
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2) |
||||
|
try { |
||||
|
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class }); |
||||
|
|
||||
|
String manufacturer = android.os.Build.MANUFACTURER; |
||||
|
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer); |
||||
|
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB && |
||||
|
android.os.Build.MANUFACTURER.contains("HTC")) |
||||
|
{ |
||||
|
gingerbread_getMethod.invoke(settings, true); |
||||
|
} |
||||
|
} catch (NoSuchMethodException e) { |
||||
|
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8"); |
||||
|
} catch (IllegalArgumentException e) { |
||||
|
Log.d(TAG, "Doing the NavDump failed with bad arguments"); |
||||
|
} catch (IllegalAccessException e) { |
||||
|
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore"); |
||||
|
} catch (InvocationTargetException e) { |
||||
|
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore."); |
||||
|
} |
||||
|
|
||||
|
//We don't save any form data in the application |
||||
|
settings.setSaveFormData(false); |
||||
|
settings.setSavePassword(false); |
||||
|
|
||||
|
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist |
||||
|
// while we do this |
||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { |
||||
|
settings.setAllowUniversalAccessFromFileURLs(true); |
||||
|
} |
||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { |
||||
|
settings.setMediaPlaybackRequiresUserGesture(false); |
||||
|
} |
||||
|
// Enable database |
||||
|
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16 |
||||
|
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); |
||||
|
settings.setDatabaseEnabled(true); |
||||
|
settings.setDatabasePath(databasePath); |
||||
|
|
||||
|
|
||||
|
//Determine whether we're in debug or release mode, and turn on Debugging! |
||||
|
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo(); |
||||
|
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 && |
||||
|
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { |
||||
|
enableRemoteDebugging(); |
||||
|
} |
||||
|
|
||||
|
settings.setGeolocationDatabasePath(databasePath); |
||||
|
|
||||
|
// Enable DOM storage |
||||
|
settings.setDomStorageEnabled(true); |
||||
|
|
||||
|
// Enable built-in geolocation |
||||
|
settings.setGeolocationEnabled(true); |
||||
|
|
||||
|
// Enable AppCache |
||||
|
// Fix for CB-2282 |
||||
|
settings.setAppCacheMaxSize(5 * 1048576); |
||||
|
settings.setAppCachePath(databasePath); |
||||
|
settings.setAppCacheEnabled(true); |
||||
|
|
||||
|
// Fix for CB-1405 |
||||
|
// Google issue 4641 |
||||
|
String defaultUserAgent = settings.getUserAgentString(); |
||||
|
|
||||
|
// Fix for CB-3360 |
||||
|
String overrideUserAgent = preferences.getString("OverrideUserAgent", null); |
||||
|
if (overrideUserAgent != null) { |
||||
|
settings.setUserAgentString(overrideUserAgent); |
||||
|
} else { |
||||
|
String appendUserAgent = preferences.getString("AppendUserAgent", null); |
||||
|
if (appendUserAgent != null) { |
||||
|
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent); |
||||
|
} |
||||
|
} |
||||
|
// End CB-3360 |
||||
|
|
||||
|
IntentFilter intentFilter = new IntentFilter(); |
||||
|
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); |
||||
|
if (this.receiver == null) { |
||||
|
this.receiver = new BroadcastReceiver() { |
||||
|
@Override |
||||
|
public void onReceive(Context context, Intent intent) { |
||||
|
settings.getUserAgentString(); |
||||
|
} |
||||
|
}; |
||||
|
webView.getContext().registerReceiver(this.receiver, intentFilter); |
||||
|
} |
||||
|
// end CB-1405 |
||||
|
} |
||||
|
|
||||
|
@TargetApi(Build.VERSION_CODES.KITKAT) |
||||
|
private void enableRemoteDebugging() { |
||||
|
try { |
||||
|
WebView.setWebContentsDebuggingEnabled(true); |
||||
|
} catch (IllegalArgumentException e) { |
||||
|
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! "); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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.pauseTimers(); |
||||
|
} else { |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,100 @@ |
|||||
|
{ |
||||
|
"prepare_queue": { |
||||
|
"installed": [], |
||||
|
"uninstalled": [] |
||||
|
}, |
||||
|
"config_munge": { |
||||
|
"files": { |
||||
|
"res/xml/config.xml": { |
||||
|
"parents": { |
||||
|
"/*": [ |
||||
|
{ |
||||
|
"xml": "<feature name=\"Device\"><param name=\"android-package\" value=\"org.apache.cordova.device.Device\" /></feature>", |
||||
|
"count": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"xml": "<feature name=\"StatusBar\"><param name=\"android-package\" value=\"org.apache.cordova.statusbar.StatusBar\" /><param name=\"onload\" value=\"true\" /></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=\"SplashScreen\"><param name=\"android-package\" value=\"org.apache.cordova.splashscreen.SplashScreen\" /><param name=\"onload\" value=\"true\" /></feature>", |
||||
|
"count": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"xml": "<feature name=\"Keyboard\"><param name=\"android-package\" value=\"io.ionic.keyboard.IonicKeyboard\" /><param name=\"onload\" value=\"true\" /></feature>", |
||||
|
"count": 1 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"installed_plugins": { |
||||
|
"cordova-plugin-device": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
}, |
||||
|
"cordova-plugin-statusbar": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
}, |
||||
|
"cordova-plugin-whitelist": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
}, |
||||
|
"cordova-plugin-splashscreen": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
}, |
||||
|
"cordova-plugin-console": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
}, |
||||
|
"ionic-plugin-keyboard": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.app751563" |
||||
|
} |
||||
|
}, |
||||
|
"dependent_plugins": {}, |
||||
|
"modules": [ |
||||
|
{ |
||||
|
"id": "cordova-plugin-device.device", |
||||
|
"file": "plugins/cordova-plugin-device/www/device.js", |
||||
|
"pluginId": "cordova-plugin-device", |
||||
|
"clobbers": [ |
||||
|
"device" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "cordova-plugin-statusbar.statusbar", |
||||
|
"file": "plugins/cordova-plugin-statusbar/www/statusbar.js", |
||||
|
"pluginId": "cordova-plugin-statusbar", |
||||
|
"clobbers": [ |
||||
|
"window.StatusBar" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "cordova-plugin-splashscreen.SplashScreen", |
||||
|
"file": "plugins/cordova-plugin-splashscreen/www/splashscreen.js", |
||||
|
"pluginId": "cordova-plugin-splashscreen", |
||||
|
"clobbers": [ |
||||
|
"navigator.splashscreen" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "ionic-plugin-keyboard.keyboard", |
||||
|
"file": "plugins/ionic-plugin-keyboard/www/android/keyboard.js", |
||||
|
"pluginId": "ionic-plugin-keyboard", |
||||
|
"clobbers": [ |
||||
|
"cordova.plugins.Keyboard" |
||||
|
], |
||||
|
"runs": true |
||||
|
} |
||||
|
], |
||||
|
"plugin_metadata": { |
||||
|
"cordova-plugin-device": "1.1.3", |
||||
|
"cordova-plugin-statusbar": "2.2.0", |
||||
|
"cordova-plugin-whitelist": "1.3.0", |
||||
|
"cordova-plugin-splashscreen": "4.0.0", |
||||
|
"cordova-plugin-console": "1.0.4", |
||||
|
"ionic-plugin-keyboard": "2.2.1" |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
}; |
@ -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); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,283 @@ |
|||||
|
/* |
||||
|
* |
||||
|
* 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 |
||||
|
}, |
||||
|
jsToNativeBridgeMode, // Set lazily.
|
||||
|
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT, |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
// 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() { |
||||
|
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; |
@ -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); |
||||
|
} |
||||
|
} |
@ -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", []); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,48 @@ |
|||||
|
cordova.define('cordova/plugin_list', function(require, exports, module) { |
||||
|
module.exports = [ |
||||
|
{ |
||||
|
"id": "cordova-plugin-device.device", |
||||
|
"file": "plugins/cordova-plugin-device/www/device.js", |
||||
|
"pluginId": "cordova-plugin-device", |
||||
|
"clobbers": [ |
||||
|
"device" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "cordova-plugin-statusbar.statusbar", |
||||
|
"file": "plugins/cordova-plugin-statusbar/www/statusbar.js", |
||||
|
"pluginId": "cordova-plugin-statusbar", |
||||
|
"clobbers": [ |
||||
|
"window.StatusBar" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "cordova-plugin-splashscreen.SplashScreen", |
||||
|
"file": "plugins/cordova-plugin-splashscreen/www/splashscreen.js", |
||||
|
"pluginId": "cordova-plugin-splashscreen", |
||||
|
"clobbers": [ |
||||
|
"navigator.splashscreen" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"id": "ionic-plugin-keyboard.keyboard", |
||||
|
"file": "plugins/ionic-plugin-keyboard/www/android/keyboard.js", |
||||
|
"pluginId": "ionic-plugin-keyboard", |
||||
|
"clobbers": [ |
||||
|
"cordova.plugins.Keyboard" |
||||
|
], |
||||
|
"runs": true |
||||
|
} |
||||
|
]; |
||||
|
module.exports.metadata = |
||||
|
// TOP OF METADATA
|
||||
|
{ |
||||
|
"cordova-plugin-device": "1.1.3", |
||||
|
"cordova-plugin-statusbar": "2.2.0", |
||||
|
"cordova-plugin-whitelist": "1.3.0", |
||||
|
"cordova-plugin-splashscreen": "4.0.0", |
||||
|
"cordova-plugin-console": "1.0.4", |
||||
|
"ionic-plugin-keyboard": "2.2.1" |
||||
|
}; |
||||
|
// BOTTOM OF METADATA
|
||||
|
}); |
@ -0,0 +1,46 @@ |
|||||
|
/* Empty. Add your own CSS if you like */ |
||||
|
.o-imgMenu{ |
||||
|
width: 25px!important; |
||||
|
height: 25px!important; |
||||
|
} |
||||
|
.o-imgTitle{ |
||||
|
width: 40px!important; |
||||
|
height: 40px!important; |
||||
|
} |
||||
|
.o-bold{ |
||||
|
font-weight: bold!important; |
||||
|
} |
||||
|
.o-float-right{ |
||||
|
float: right; |
||||
|
} |
||||
|
.o-text-right{ |
||||
|
text-align: right; |
||||
|
} |
||||
|
.o-mini-text{ |
||||
|
font-size: 12px; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
.o-img-joined{ |
||||
|
width: 20px!important; |
||||
|
height: 20px!important; |
||||
|
} |
||||
|
.o-img-new{ |
||||
|
width: 30px!important; |
||||
|
height: 30px!important; |
||||
|
} |
||||
|
.o-badgeCollectivized{ |
||||
|
background: #33CD5F; |
||||
|
padding: 5px; |
||||
|
border-radius: 5px; |
||||
|
font-size: 12px; |
||||
|
color: #ffffff; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
.o-badge-calm{ |
||||
|
background: #11C1F3; |
||||
|
padding: 5px; |
||||
|
border-radius: 7px; |
||||
|
font-size: 12px; |
||||
|
color: #ffffff; |
||||
|
font-weight: bold; |
||||
|
} |