Removes MiRCART-cordoba.
1
MiRCART-cordoba/LICENCE
vendored
@ -1 +0,0 @@
|
||||
../assets/text/LICENCE.asdf
|
27
MiRCART-cordoba/config.xml
vendored
@ -1,27 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="com.lalbornoz.MiRCART" version="1.1.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<author email="lucio@lucioillanes.de" href="https://github.com/lalbornoz/MiRCART#readme">
|
||||
Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>, based on work by JOLLO NET NA
|
||||
</author>
|
||||
<description>
|
||||
MiRCART (standalone Apache Córdoba app)
|
||||
</description>
|
||||
<name>MiRCART-cordoba</name>
|
||||
<access origin="*" />
|
||||
<content src="index.html" />
|
||||
<plugin name="cordova-plugin-whitelist" spec="1" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<platform name="android">
|
||||
<allow-intent href="market:*" />
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
<allow-intent href="itms:*" />
|
||||
<allow-intent href="itms-apps:*" />
|
||||
</platform>
|
||||
<engine name="android" spec="~7.1.3" />
|
||||
</widget>
|
1
MiRCART-cordoba/deploy.sh
vendored
@ -1 +0,0 @@
|
||||
../assets/shell/deploy-cordoba.sh
|
218
MiRCART-cordoba/package-lock.json
generated
vendored
@ -1,218 +0,0 @@
|
||||
{
|
||||
"name": "MiRCART-cordoba",
|
||||
"version": "1.1.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"cordova-android": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-7.1.3.tgz",
|
||||
"integrity": "sha512-nXGtnMS17EX+AIJllHflj3lwcYKl00KwM9No/6IINhOs0gJ5hAg1I2JlCzDlkUiWwSr2nmIOAYGeQmxnkP2s2Q==",
|
||||
"requires": {
|
||||
"abbrev": "*",
|
||||
"android-versions": "1.3.0",
|
||||
"ansi": "*",
|
||||
"balanced-match": "*",
|
||||
"base64-js": "1.2.0",
|
||||
"big-integer": "1.6.32",
|
||||
"bplist-parser": "*",
|
||||
"brace-expansion": "*",
|
||||
"concat-map": "*",
|
||||
"cordova-common": "2.2.5",
|
||||
"cordova-registry-mapper": "*",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "5.0.15",
|
||||
"inflight": "*",
|
||||
"inherits": "*",
|
||||
"minimatch": "*",
|
||||
"nopt": "3.0.1",
|
||||
"once": "*",
|
||||
"path-is-absolute": "1.0.1",
|
||||
"plist": "2.1.0",
|
||||
"properties-parser": "0.2.3",
|
||||
"q": "1.4.1",
|
||||
"sax": "0.3.5",
|
||||
"semver": "5.5.0",
|
||||
"shelljs": "0.5.3",
|
||||
"underscore": "*",
|
||||
"unorm": "*",
|
||||
"wrappy": "*",
|
||||
"xmlbuilder": "8.2.2",
|
||||
"xmldom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"android-versions": {
|
||||
"version": "1.3.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"ansi": {
|
||||
"version": "0.3.1",
|
||||
"bundled": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.32",
|
||||
"bundled": true
|
||||
},
|
||||
"bplist-parser": {
|
||||
"version": "0.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"big-integer": "^1.6.7"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"cordova-common": {
|
||||
"version": "2.2.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ansi": "^0.3.1",
|
||||
"bplist-parser": "^0.1.0",
|
||||
"cordova-registry-mapper": "^1.1.8",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "^5.0.13",
|
||||
"minimatch": "^3.0.0",
|
||||
"plist": "^2.1.0",
|
||||
"q": "^1.4.1",
|
||||
"shelljs": "^0.5.3",
|
||||
"underscore": "^1.8.3",
|
||||
"unorm": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"cordova-registry-mapper": {
|
||||
"version": "1.1.15",
|
||||
"bundled": true
|
||||
},
|
||||
"elementtree": {
|
||||
"version": "0.1.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"sax": "0.3.5"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "5.0.15",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "2.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "1.2.0",
|
||||
"xmlbuilder": "8.2.2",
|
||||
"xmldom": "0.1.x"
|
||||
}
|
||||
},
|
||||
"properties-parser": {
|
||||
"version": "0.2.3",
|
||||
"bundled": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.3.5",
|
||||
"bundled": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"bundled": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.3",
|
||||
"bundled": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.9.1",
|
||||
"bundled": true
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "8.2.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.27",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
MiRCART-cordoba/package.json
vendored
@ -1,34 +0,0 @@
|
||||
{
|
||||
"author": "Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>, based on work by JOLLO NET NA",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lalbornoz/MiRCART/issues"
|
||||
},
|
||||
"cordova": {
|
||||
"plugins": {
|
||||
"cordova-plugin-whitelist": {}
|
||||
},
|
||||
"platforms": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"cordova-android": "^7.1.3",
|
||||
"cordova-plugin-whitelist": "^1.3.3"
|
||||
},
|
||||
"description": "MiRCART (standalone Apache Córdoba app)",
|
||||
"directories": {
|
||||
"www": "www"
|
||||
},
|
||||
"homepage": "https://github.com/lalbornoz/MiRCART#readme",
|
||||
"license": "Jollo LNT license <https://raw.githubusercontent.com/lalbornoz/MiRCART/master/LICENCE>",
|
||||
"main": "index.html",
|
||||
"name": "MiRCART-cordoba",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lalbornoz/MiRCART.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"version": "1.1.2"
|
||||
}
|
14
MiRCART-cordoba/platforms/android/.gitignore
vendored
@ -1,14 +0,0 @@
|
||||
# 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
|
@ -1,23 +0,0 @@
|
||||
<?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="19" />
|
||||
</manifest>
|
@ -1,137 +0,0 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
ext {
|
||||
apply from: 'cordova.gradle'
|
||||
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
group = 'org.apache.cordova'
|
||||
version = '7.1.3'
|
||||
|
||||
android {
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
publishNonDefault true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
}
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
pom {
|
||||
project {
|
||||
packaging 'aar'
|
||||
name 'Cordova'
|
||||
url 'https://cordova.apache.org'
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id 'stevengill'
|
||||
name 'Steve Gill'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
developerConnection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
url 'https://git-wip-us.apache.org/repos/asf?p=cordova-android'
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
bintray {
|
||||
user = System.getenv('BINTRAY_USER')
|
||||
key = System.getenv('BINTRAY_KEY')
|
||||
configurations = ['archives']
|
||||
pkg {
|
||||
repo = 'maven'
|
||||
name = 'cordova-android'
|
||||
userOrg = 'cordova'
|
||||
licenses = ['Apache-2.0']
|
||||
vcsUrl = 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
websiteUrl = 'https://cordova.apache.org'
|
||||
issueTrackerUrl = 'https://issues.apache.org/jira/browse/CB'
|
||||
publicDownloadNumbers = true
|
||||
licenses = ['Apache-2.0']
|
||||
labels = ['android', 'cordova', 'phonegap']
|
||||
version {
|
||||
name = '7.1.3'
|
||||
released = new Date()
|
||||
vcsTag = '7.1.3'
|
||||
}
|
||||
}
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import groovy.swing.SwingBuilder
|
||||
|
||||
String doEnsureValueExists(filePath, props, key) {
|
||||
if (props.get(key) == null) {
|
||||
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||
}
|
||||
return props.get(key)
|
||||
}
|
||||
|
||||
String doGetProjectTarget() {
|
||||
def props = new Properties()
|
||||
def propertiesFile = 'project.properties';
|
||||
if(!(file(propertiesFile).exists())) {
|
||||
propertiesFile = '../project.properties';
|
||||
}
|
||||
file(propertiesFile).withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
return doEnsureValueExists('project.properties', props, 'target')
|
||||
}
|
||||
|
||||
String[] getAvailableBuildTools() {
|
||||
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||
buildToolsDir.list()
|
||||
.findAll { it ==~ /[0-9.]+/ }
|
||||
.sort { a, b -> compareVersions(b, a) }
|
||||
}
|
||||
|
||||
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
|
||||
def availableBuildToolsVersions
|
||||
try {
|
||||
availableBuildToolsVersions = getAvailableBuildTools()
|
||||
} catch (e) {
|
||||
println "An exception occurred while trying to find the Android build tools."
|
||||
throw e
|
||||
}
|
||||
if (availableBuildToolsVersions.length > 0) {
|
||||
def highestBuildToolsVersion = availableBuildToolsVersions[0]
|
||||
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
|
||||
throw new RuntimeException(
|
||||
"No usable Android build tools found. Highest installed version is " +
|
||||
highestBuildToolsVersion + "; minimum version required is " +
|
||||
minBuildToolsVersion + ".")
|
||||
}
|
||||
highestBuildToolsVersion
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No installed build tools found. Install the Android build tools version " +
|
||||
minBuildToolsVersion + " or higher.")
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first non-zero result of subtracting version list elements
|
||||
// pairwise. If they are all identical, return the difference in length of
|
||||
// the two lists.
|
||||
int compareVersionList(Collection aParts, Collection bParts) {
|
||||
def pairs = ([aParts, bParts]).transpose()
|
||||
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
|
||||
}
|
||||
|
||||
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
|
||||
// elements are identical, the longer version is the largest by this method.
|
||||
// Examples:
|
||||
// "19.0.0" > "19"
|
||||
// "19.0.1" > "19.0.0"
|
||||
// "19.1.0" > "19.0.1"
|
||||
// "19" > "18.999.999"
|
||||
int compareVersions(String a, String b) {
|
||||
def aParts = a.tokenize('.').collect {it.toInteger()}
|
||||
def bParts = b.tokenize('.').collect {it.toInteger()}
|
||||
compareVersionList(aParts, bParts)
|
||||
}
|
||||
|
||||
String getAndroidSdkDir() {
|
||||
def rootDir = project.rootDir
|
||||
def androidSdkDir = null
|
||||
String envVar = System.getenv("ANDROID_HOME")
|
||||
def localProperties = new File(rootDir, 'local.properties')
|
||||
String systemProperty = System.getProperty("android.home")
|
||||
if (envVar != null) {
|
||||
androidSdkDir = envVar
|
||||
} else if (localProperties.exists()) {
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream { instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDirProp = properties.getProperty('sdk.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = sdkDirProp
|
||||
} else {
|
||||
sdkDirProp = properties.getProperty('android.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (androidSdkDir == null && systemProperty != null) {
|
||||
androidSdkDir = systemProperty
|
||||
}
|
||||
if (androidSdkDir == null) {
|
||||
throw new RuntimeException(
|
||||
"Unable to determine Android SDK directory.")
|
||||
}
|
||||
androidSdkDir
|
||||
}
|
||||
|
||||
def doExtractIntFromManifest(name) {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return new BigInteger(matcher.group(1))
|
||||
}
|
||||
|
||||
def doExtractStringFromManifest(name) {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile(name + "=\"(\\S+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return matcher.group(1)
|
||||
}
|
||||
|
||||
def doPromptForPassword(msg) {
|
||||
if (System.console() == null) {
|
||||
def ret = null
|
||||
new SwingBuilder().edt {
|
||||
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
|
||||
vbox {
|
||||
label(text: msg)
|
||||
def input = passwordField()
|
||||
button(defaultButton: true, text: 'OK', actionPerformed: {
|
||||
ret = input.password;
|
||||
dispose();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
throw new GradleException('User canceled build')
|
||||
}
|
||||
return new String(ret)
|
||||
} else {
|
||||
return System.console().readPassword('\n' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
def doGetConfigXml() {
|
||||
def xml = file("src/main/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) }
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
# 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-27
|
||||
apk-configurations=
|
||||
renderscript.opt.level=O0
|
||||
android.library=true
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
/*
|
||||
* This is a utility class that allows us to get the BuildConfig variable, which is required
|
||||
* for the use of different providers. This is not guaranteed to work, and it's better for this
|
||||
* to be set in the build step in config.xml
|
||||
*
|
||||
*/
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
public class BuildHelper {
|
||||
|
||||
|
||||
private static String TAG="BuildHelper";
|
||||
|
||||
/*
|
||||
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||
* that read the Build Configuration.
|
||||
*
|
||||
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||
* StackOverflow. This is annoying as hell! However, this method does not work with
|
||||
* ProGuard, and you should use the config.xml to define the application_id
|
||||
*
|
||||
*/
|
||||
|
||||
public static Object getBuildConfigValue(Context ctx, String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
|
||||
Field field = clazz.getField(key);
|
||||
return field.get(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.PluginResult;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CallbackContext {
|
||||
private static final String LOG_TAG = "CordovaPlugin";
|
||||
|
||||
private String callbackId;
|
||||
private CordovaWebView webView;
|
||||
protected boolean finished;
|
||||
private int changingThreads;
|
||||
|
||||
public CallbackContext(String callbackId, CordovaWebView webView) {
|
||||
this.callbackId = callbackId;
|
||||
this.webView = webView;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public boolean isChangingThreads() {
|
||||
return changingThreads > 0;
|
||||
}
|
||||
|
||||
public String getCallbackId() {
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
public void sendPluginResult(PluginResult pluginResult) {
|
||||
synchronized (this) {
|
||||
if (finished) {
|
||||
LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
|
||||
return;
|
||||
} else {
|
||||
finished = !pluginResult.getKeepCallback();
|
||||
}
|
||||
}
|
||||
webView.sendPluginResult(pluginResult, callbackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(JSONObject message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(String message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(JSONArray message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(byte[] message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(int message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*/
|
||||
public void success() {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(JSONObject message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(String message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(int message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
|
||||
public class Config {
|
||||
private static final String TAG = "Config";
|
||||
|
||||
static ConfigXmlParser parser;
|
||||
|
||||
private Config() {
|
||||
}
|
||||
|
||||
public static void init(Activity action) {
|
||||
parser = new ConfigXmlParser();
|
||||
parser.parse(action);
|
||||
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
|
||||
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
|
||||
}
|
||||
|
||||
// Intended to be used for testing only; creates an empty configuration.
|
||||
public static void init() {
|
||||
if (parser == null) {
|
||||
parser = new ConfigXmlParser();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getStartUrl() {
|
||||
if (parser == null) {
|
||||
return "file:///android_asset/www/index.html";
|
||||
}
|
||||
return parser.getLaunchUrl();
|
||||
}
|
||||
|
||||
public static String getErrorUrl() {
|
||||
return parser.getPreferences().getString("errorurl", null);
|
||||
}
|
||||
|
||||
public static List<PluginEntry> getPluginEntries() {
|
||||
return parser.getPluginEntries();
|
||||
}
|
||||
|
||||
public static CordovaPreferences getPreferences() {
|
||||
return parser.getPreferences();
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return parser != null;
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,519 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* This class is the main Android activity that represents the Cordova
|
||||
* application. It should be extended by the user to load the specific
|
||||
* html file that contains the application.
|
||||
*
|
||||
* As an example:
|
||||
*
|
||||
* <pre>
|
||||
* package org.apache.cordova.examples;
|
||||
*
|
||||
* import android.os.Bundle;
|
||||
* import org.apache.cordova.*;
|
||||
*
|
||||
* public class Example extends CordovaActivity {
|
||||
* @Override
|
||||
* public void onCreate(Bundle savedInstanceState) {
|
||||
* super.onCreate(savedInstanceState);
|
||||
* super.init();
|
||||
* // Load your application
|
||||
* loadUrl(launchUrl);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Cordova xml configuration: Cordova uses a configuration file at
|
||||
* res/xml/config.xml to specify its settings. See "The config.xml File"
|
||||
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
|
||||
* for the configuration. The use of the set*Property() methods is
|
||||
* deprecated in favor of the config.xml file.
|
||||
*
|
||||
*/
|
||||
public class CordovaActivity extends Activity {
|
||||
public static String TAG = "CordovaActivity";
|
||||
|
||||
// The webview for our app
|
||||
protected CordovaWebView appView;
|
||||
|
||||
private static int ACTIVITY_STARTING = 0;
|
||||
private static int ACTIVITY_RUNNING = 1;
|
||||
private static int ACTIVITY_EXITING = 2;
|
||||
|
||||
// Keep app running when pause is received. (default = true)
|
||||
// If true, then the JavaScript and native code continue to run in the background
|
||||
// when another application (activity) is started.
|
||||
protected boolean keepRunning = true;
|
||||
|
||||
// Flag to keep immersive mode if set to fullscreen
|
||||
protected boolean immersiveMode;
|
||||
|
||||
// Read from config.xml:
|
||||
protected CordovaPreferences preferences;
|
||||
protected String launchUrl;
|
||||
protected ArrayList<PluginEntry> pluginEntries;
|
||||
protected CordovaInterfaceImpl cordovaInterface;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
|
||||
loadConfig();
|
||||
|
||||
String logLevel = preferences.getString("loglevel", "ERROR");
|
||||
LOG.setLogLevel(logLevel);
|
||||
|
||||
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
|
||||
LOG.d(TAG, "CordovaActivity.onCreate()");
|
||||
|
||||
if (!preferences.getBoolean("ShowTitle", false)) {
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
if (preferences.getBoolean("SetFullscreen", false)) {
|
||||
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
|
||||
preferences.set("Fullscreen", true);
|
||||
}
|
||||
if (preferences.getBoolean("Fullscreen", false)) {
|
||||
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
|
||||
// (as was the case in previous cordova versions)
|
||||
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
||||
immersiveMode = true;
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
cordovaInterface = makeCordovaInterface();
|
||||
if (savedInstanceState != null) {
|
||||
cordovaInterface.restoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
appView = makeWebView();
|
||||
createViews();
|
||||
if (!appView.isInitialized()) {
|
||||
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||
}
|
||||
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
||||
|
||||
// Wire the hardware volume controls to control media if desired.
|
||||
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
||||
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void loadConfig() {
|
||||
ConfigXmlParser parser = new ConfigXmlParser();
|
||||
parser.parse(this);
|
||||
preferences = parser.getPreferences();
|
||||
preferences.setPreferencesBundle(getIntent().getExtras());
|
||||
launchUrl = parser.getLaunchUrl();
|
||||
pluginEntries = parser.getPluginEntries();
|
||||
Config.parser = parser;
|
||||
}
|
||||
|
||||
//Suppressing warnings in AndroidStudio
|
||||
@SuppressWarnings({"deprecation", "ResourceType"})
|
||||
protected void createViews() {
|
||||
//Why are we setting a constant as the ID? This should be investigated
|
||||
appView.getView().setId(100);
|
||||
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
setContentView(appView.getView());
|
||||
|
||||
if (preferences.contains("BackgroundColor")) {
|
||||
try {
|
||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||
// Background of activity:
|
||||
appView.getView().setBackgroundColor(backgroundColor);
|
||||
}
|
||||
catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
appView.getView().requestFocusFromTouch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the default web view object.
|
||||
* <p/>
|
||||
* Override this to customize the webview that is used.
|
||||
*/
|
||||
protected CordovaWebView makeWebView() {
|
||||
return new CordovaWebViewImpl(makeWebViewEngine());
|
||||
}
|
||||
|
||||
protected CordovaWebViewEngine makeWebViewEngine() {
|
||||
return CordovaWebViewImpl.createEngine(this, preferences);
|
||||
}
|
||||
|
||||
protected CordovaInterfaceImpl makeCordovaInterface() {
|
||||
return new CordovaInterfaceImpl(this) {
|
||||
@Override
|
||||
public Object onMessage(String id, Object data) {
|
||||
// Plumb this to CordovaActivity.onMessage for backwards compatibility
|
||||
return CordovaActivity.this.onMessage(id, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
public void loadUrl(String url) {
|
||||
if (appView == null) {
|
||||
init();
|
||||
}
|
||||
|
||||
// If keepRunning
|
||||
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
||||
|
||||
appView.loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*/
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LOG.d(TAG, "Paused the activity.");
|
||||
|
||||
if (this.appView != null) {
|
||||
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
||||
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
||||
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
||||
this.appView.handlePause(keepRunning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
//Forward to plugins
|
||||
if (this.appView != null)
|
||||
this.appView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LOG.d(TAG, "Resumed the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
// Force window to have focus, so application always
|
||||
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
|
||||
this.getWindow().getDecorView().requestFocus();
|
||||
|
||||
this.appView.handleResume(this.keepRunning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
LOG.d(TAG, "Stopped the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
LOG.d(TAG, "Started the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LOG.d(TAG, "CordovaActivity.onDestroy()");
|
||||
super.onDestroy();
|
||||
|
||||
if (this.appView != null) {
|
||||
appView.handleDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when view focus is changed
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && immersiveMode) {
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
|
||||
cordovaInterface.setActivityResultRequestCode(requestCode);
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
||||
* the resultCode it returned, and any additional data from it.
|
||||
*
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
cordovaInterface.onActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||
*
|
||||
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||
* @param description A String describing the error.
|
||||
* @param failingUrl The url that failed to load.
|
||||
*/
|
||||
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
|
||||
final CordovaActivity me = this;
|
||||
|
||||
// If errorUrl specified, then load it
|
||||
final String errorUrl = preferences.getString("errorUrl", null);
|
||||
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
me.appView.showWebPage(errorUrl, false, true, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
// If not, then display error dialog
|
||||
else {
|
||||
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (exit) {
|
||||
me.appView.getView().setVisibility(View.GONE);
|
||||
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error dialog and optionally exit application.
|
||||
*/
|
||||
public void displayError(final String title, final String message, final String button, final boolean exit) {
|
||||
final CordovaActivity me = this;
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle(title);
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(button,
|
||||
new AlertDialog.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
if (exit) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
dlg.create();
|
||||
dlg.show();
|
||||
} catch (Exception e) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook in Cordova for menu plugins
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data) {
|
||||
if ("onReceivedError".equals(id)) {
|
||||
JSONObject d = (JSONObject) data;
|
||||
try {
|
||||
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if ("exit".equals(id)) {
|
||||
finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
cordovaInterface.onSaveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
PluginManager pm = this.appView.getPluginManager();
|
||||
if (pm != null) {
|
||||
pm.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the user grants permissions
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String permissions[],
|
||||
int[] grantResults) {
|
||||
try
|
||||
{
|
||||
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
public class CordovaBridge {
|
||||
private static final String LOG_TAG = "CordovaBridge";
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
|
||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
}
|
||||
|
||||
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
if (!verifySecret("exec()", bridgeSecret)) {
|
||||
return null;
|
||||
}
|
||||
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
|
||||
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
|
||||
if (arguments == null) {
|
||||
return "@Null arguments.";
|
||||
}
|
||||
|
||||
jsMessageQueue.setPaused(true);
|
||||
try {
|
||||
// Tell the resourceApi what thread the JS is running on.
|
||||
CordovaResourceApi.jsThread = Thread.currentThread();
|
||||
|
||||
pluginManager.exec(service, action, callbackId, arguments);
|
||||
String ret = null;
|
||||
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
|
||||
ret = jsMessageQueue.popAndEncode(false);
|
||||
}
|
||||
return ret;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} finally {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
|
||||
return;
|
||||
}
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
}
|
||||
|
||||
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
|
||||
return null;
|
||||
}
|
||||
return jsMessageQueue.popAndEncode(fromOnlineEvent);
|
||||
}
|
||||
|
||||
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
|
||||
if (!jsMessageQueue.isBridgeEnabled()) {
|
||||
if (bridgeSecret == -1) {
|
||||
LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
|
||||
} else {
|
||||
LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Bridge secret wrong and bridge not due to it being from the previous page.
|
||||
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
|
||||
LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
|
||||
clearBridgeSecret();
|
||||
throw new IllegalAccessException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Called on page transitions */
|
||||
void clearBridgeSecret() {
|
||||
expectedBridgeSecret = -1;
|
||||
}
|
||||
|
||||
public boolean isSecretEstablished() {
|
||||
return expectedBridgeSecret != -1;
|
||||
}
|
||||
|
||||
/** Called by cordova.js to initialize the bridge. */
|
||||
//On old Androids SecureRandom isn't really secure, this is the least of your problems if
|
||||
//you're running Android 4.3 and below in 2017
|
||||
@SuppressLint("TrulyRandom")
|
||||
int generateBridgeSecret() {
|
||||
SecureRandom randGen = new SecureRandom();
|
||||
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
|
||||
return expectedBridgeSecret;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
jsMessageQueue.reset();
|
||||
clearBridgeSecret();
|
||||
}
|
||||
|
||||
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
|
||||
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
|
||||
JSONArray array;
|
||||
try {
|
||||
array = new JSONArray(defaultValue.substring(4));
|
||||
int bridgeSecret = array.getInt(0);
|
||||
String service = array.getString(1);
|
||||
String action = array.getString(2);
|
||||
String callbackId = array.getString(3);
|
||||
String r = jsExec(bridgeSecret, service, action, callbackId, message);
|
||||
return r == null ? "" : r;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Sets the native->JS bridge mode.
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
|
||||
try {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
|
||||
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
|
||||
} catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Polling for JavaScript messages
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
|
||||
try {
|
||||
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
|
||||
return r == null ? "" : r;
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
// Protect against random iframes being able to talk through the bridge.
|
||||
// Trust only pages which the app would have been allowed to navigate to anyway.
|
||||
if (pluginManager.shouldAllowBridgeAccess(origin)) {
|
||||
// Enable the bridge
|
||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||
// Tell JS the bridge secret.
|
||||
int secret = generateBridgeSecret();
|
||||
return ""+secret;
|
||||
} else {
|
||||
LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.webkit.ClientCertRequest;
|
||||
|
||||
/**
|
||||
* Implementation of the ICordovaClientCertRequest for Android WebView.
|
||||
*
|
||||
*/
|
||||
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
|
||||
|
||||
private final ClientCertRequest request;
|
||||
|
||||
public CordovaClientCertRequest(ClientCertRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel this request
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void cancel()
|
||||
{
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the host name of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public String getHost()
|
||||
{
|
||||
return request.getHost();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable types of asymmetric keys (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public String[] getKeyTypes()
|
||||
{
|
||||
return request.getKeyTypes();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the port number of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public int getPort()
|
||||
{
|
||||
return request.getPort();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public Principal[] getPrincipals()
|
||||
{
|
||||
return request.getPrincipals();
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore the request for now. Do not remember user's choice.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void ignore()
|
||||
{
|
||||
request.ignore();
|
||||
}
|
||||
|
||||
/*
|
||||
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||
*
|
||||
* @param privateKey The privateKey
|
||||
* @param chain The certificate chain
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
|
||||
{
|
||||
request.proceed(privateKey, chain);
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* The Activity interface that is implemented by CordovaActivity.
|
||||
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
|
||||
*/
|
||||
public interface CordovaInterface {
|
||||
|
||||
/**
|
||||
* Launch an activity for which you would like a result when it finished. When this activity exits,
|
||||
* your onActivityResult() method will be called.
|
||||
*
|
||||
* @param command The command object
|
||||
* @param intent The intent to start
|
||||
* @param requestCode The request code that is passed to callback to identify the activity
|
||||
*/
|
||||
abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
|
||||
|
||||
/**
|
||||
* Set the plugin to be called when a sub-activity exits.
|
||||
*
|
||||
* @param plugin The plugin on which onActivityResult is to be called
|
||||
*/
|
||||
abstract public void setActivityResultCallback(CordovaPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the Android activity.
|
||||
*
|
||||
* If a custom engine lives outside of the Activity's lifecycle the return value may be null.
|
||||
*
|
||||
* @return the Activity
|
||||
*/
|
||||
public abstract Activity getActivity();
|
||||
|
||||
/**
|
||||
* Get the Android context.
|
||||
*
|
||||
* @return the Context
|
||||
*/
|
||||
public Context getContext();
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data);
|
||||
|
||||
/**
|
||||
* Returns a shared thread pool that can be used for background tasks.
|
||||
*/
|
||||
public ExecutorService getThreadPool();
|
||||
|
||||
/**
|
||||
* Sends a permission request to the activity for one permission.
|
||||
*/
|
||||
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
|
||||
|
||||
/**
|
||||
* Sends a permission request to the activity for a group of permissions
|
||||
*/
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
|
||||
|
||||
/**
|
||||
* Check for a permission. Returns true if the permission is granted, false otherwise.
|
||||
*/
|
||||
public boolean hasPermission(String permission);
|
||||
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Default implementation of CordovaInterface.
|
||||
*/
|
||||
public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
private static final String TAG = "CordovaInterfaceImpl";
|
||||
protected Activity activity;
|
||||
protected ExecutorService threadPool;
|
||||
protected PluginManager pluginManager;
|
||||
|
||||
protected ActivityResultHolder savedResult;
|
||||
protected CallbackMap permissionResultCallbacks;
|
||||
protected CordovaPlugin activityResultCallback;
|
||||
protected String initCallbackService;
|
||||
protected int activityResultRequestCode;
|
||||
protected boolean activityWasDestroyed = false;
|
||||
protected Bundle savedPluginState;
|
||||
|
||||
public CordovaInterfaceImpl(Activity activity) {
|
||||
this(activity, Executors.newCachedThreadPool());
|
||||
}
|
||||
|
||||
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
|
||||
this.activity = activity;
|
||||
this.threadPool = threadPool;
|
||||
this.permissionResultCallbacks = new CallbackMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
|
||||
setActivityResultCallback(command);
|
||||
try {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
} catch (RuntimeException e) { // E.g.: ActivityNotFoundException
|
||||
activityResultCallback = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActivityResultCallback(CordovaPlugin plugin) {
|
||||
// Cancel any previously pending activity.
|
||||
if (activityResultCallback != null) {
|
||||
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
|
||||
}
|
||||
activityResultCallback = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onMessage(String id, Object data) {
|
||||
if ("exit".equals(id)) {
|
||||
activity.finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getThreadPool() {
|
||||
return threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches any pending onActivityResult callbacks and sends the resume event if the
|
||||
* Activity was destroyed by the OS.
|
||||
*/
|
||||
public void onCordovaInit(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
if (savedResult != null) {
|
||||
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
|
||||
} else if(activityWasDestroyed) {
|
||||
// If there was no Activity result, we still need to send out the resume event if the
|
||||
// Activity was destroyed by the OS
|
||||
activityWasDestroyed = false;
|
||||
if(pluginManager != null)
|
||||
{
|
||||
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
if(appPlugin != null) {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("action", "resume");
|
||||
} catch (JSONException e) {
|
||||
LOG.e(TAG, "Failed to create event message", e);
|
||||
}
|
||||
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
|
||||
*/
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
CordovaPlugin callback = activityResultCallback;
|
||||
if(callback == null && initCallbackService != null) {
|
||||
// The application was restarted, but had defined an initial callback
|
||||
// before being shut down.
|
||||
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
|
||||
if (pluginManager != null) {
|
||||
callback = pluginManager.getPlugin(initCallbackService);
|
||||
if(callback != null) {
|
||||
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
|
||||
new ResumeCallback(callback.getServiceName(), pluginManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
activityResultCallback = null;
|
||||
|
||||
if (callback != null) {
|
||||
LOG.d(TAG, "Sending activity result to plugin");
|
||||
initCallbackService = null;
|
||||
savedResult = null;
|
||||
callback.onActivityResult(requestCode, resultCode, intent);
|
||||
return true;
|
||||
}
|
||||
LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from your startActivityForResult() overload. This is required to catch the case
|
||||
* where plugins use Activity.startActivityForResult() + CordovaInterface.setActivityResultCallback()
|
||||
* rather than CordovaInterface.startActivityForResult().
|
||||
*/
|
||||
public void setActivityResultRequestCode(int requestCode) {
|
||||
activityResultRequestCode = requestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves parameters for startActivityForResult().
|
||||
*/
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if (activityResultCallback != null) {
|
||||
String serviceName = activityResultCallback.getServiceName();
|
||||
outState.putString("callbackService", serviceName);
|
||||
}
|
||||
if(pluginManager != null){
|
||||
outState.putBundle("plugin", pluginManager.onSaveInstanceState());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
|
||||
*/
|
||||
public void restoreInstanceState(Bundle savedInstanceState) {
|
||||
initCallbackService = savedInstanceState.getString("callbackService");
|
||||
savedPluginState = savedInstanceState.getBundle("plugin");
|
||||
activityWasDestroyed = true;
|
||||
}
|
||||
|
||||
private static class ActivityResultHolder {
|
||||
private int requestCode;
|
||||
private int resultCode;
|
||||
private Intent intent;
|
||||
|
||||
public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
|
||||
this.requestCode = requestCode;
|
||||
this.resultCode = resultCode;
|
||||
this.intent = intent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the user grants permissions
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
public void onRequestPermissionResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) throws JSONException {
|
||||
Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
|
||||
if(callback != null) {
|
||||
callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||
String[] permissions = new String [1];
|
||||
permissions[0] = permission;
|
||||
requestPermissions(plugin, requestCode, permissions);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
|
||||
int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
|
||||
getActivity().requestPermissions(permissions, mappedRequestCode);
|
||||
}
|
||||
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
{
|
||||
int result = activity.checkSelfPermission(permission);
|
||||
return PackageManager.PERMISSION_GRANTED == result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,422 +0,0 @@
|
||||
/*
|
||||
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 {
|
||||
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,472 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
length = inputStream.available();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
/**
|
||||
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||
* This is an interface so that it can be easily mocked in tests.
|
||||
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||
* are not expected to implement it.
|
||||
*/
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "7.1.3";
|
||||
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||
|
||||
boolean isInitialized();
|
||||
|
||||
View getView();
|
||||
|
||||
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||
|
||||
void stopLoading();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** Use parameter-less overload */
|
||||
@Deprecated
|
||||
void clearCache(boolean b);
|
||||
|
||||
void clearHistory();
|
||||
|
||||
boolean backHistory();
|
||||
|
||||
void handlePause(boolean keepRunning);
|
||||
|
||||
void onNewIntent(Intent intent);
|
||||
|
||||
void handleResume(boolean keepRunning);
|
||||
|
||||
void handleStart();
|
||||
|
||||
void handleStop();
|
||||
|
||||
void handleDestroy();
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
*
|
||||
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||
* Instead of executing snippets of JS, you should use the exec bridge
|
||||
* to create a Java->JS communication channel.
|
||||
* To do this:
|
||||
* 1. Within plugin.xml (to have your JS run before deviceready):
|
||||
* <js-module><runs/></js-module>
|
||||
* 2. Within your .js (call exec on start-up):
|
||||
* require('cordova/channel').onCordovaReady.subscribe(function() {
|
||||
* require('cordova/exec')(win, null, 'Plugin', 'method', []);
|
||||
* function win(message) {
|
||||
* ... process message from java here ...
|
||||
* }
|
||||
* });
|
||||
* 3. Within your .java:
|
||||
* PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
|
||||
* dataResult.setKeepCallback(true);
|
||||
* savedCallbackContext.sendPluginResult(dataResult);
|
||||
*/
|
||||
@Deprecated
|
||||
void sendJavascript(String statememt);
|
||||
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
*
|
||||
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
|
||||
*
|
||||
* @param url The url to load.
|
||||
* @param openExternal Load url in browser instead of Cordova webview.
|
||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||
* @param params Parameters for new app
|
||||
*/
|
||||
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCustomViewShowing();
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void showCustomView(View view, CustomViewCallback callback);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void hideCustomView();
|
||||
|
||||
CordovaResourceApi getResourceApi();
|
||||
|
||||
void setButtonPlumbedToJs(int keyCode, boolean override);
|
||||
boolean isButtonPlumbedToJs(int keyCode);
|
||||
|
||||
void sendPluginResult(PluginResult cr, String callbackId);
|
||||
|
||||
PluginManager getPluginManager();
|
||||
CordovaWebViewEngine getEngine();
|
||||
CordovaPreferences getPreferences();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
|
||||
String getUrl();
|
||||
|
||||
// TODO: Work on deleting these by removing refs from plugins.
|
||||
Context getContext();
|
||||
void loadUrl(String url);
|
||||
Object postMessage(String id, Object data);
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
/**
|
||||
* Interface for all Cordova engines.
|
||||
* No methods will be added to this class (in order to be compatible with existing engines).
|
||||
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
|
||||
*/
|
||||
public interface CordovaWebViewEngine {
|
||||
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue);
|
||||
|
||||
CordovaWebView getCordovaWebView();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
View getView();
|
||||
|
||||
void loadUrl(String url, boolean clearNavigationStack);
|
||||
|
||||
void stopLoading();
|
||||
|
||||
/** Return the currently loaded URL */
|
||||
String getUrl();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** After calling clearHistory(), canGoBack() should be false. */
|
||||
void clearHistory();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
/** Returns whether a navigation occurred */
|
||||
boolean goBack();
|
||||
|
||||
/** Pauses / resumes the WebView's event loop. */
|
||||
void setPaused(boolean value);
|
||||
|
||||
/** Clean up all resources associated with the WebView. */
|
||||
void destroy();
|
||||
|
||||
/** Add the evaulate Javascript method **/
|
||||
void evaluateJavascript(String js, ValueCallback<String> callback);
|
||||
|
||||
/**
|
||||
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
|
||||
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
|
||||
*/
|
||||
public interface EngineView {
|
||||
CordovaWebView getCordovaWebView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
|
||||
* Methods may be added in future cordova versions, but never removed.
|
||||
*/
|
||||
public interface Client {
|
||||
Boolean onDispatchKeyEvent(KeyEvent event);
|
||||
void clearLoadTimeoutTimer();
|
||||
void onPageStarted(String newUrl);
|
||||
void onReceivedError(int errorCode, String description, String failingUrl);
|
||||
void onPageFinishedLoading(String url);
|
||||
boolean onNavigationAttempt(String url);
|
||||
}
|
||||
}
|
@ -1,615 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||
*/
|
||||
public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
public static final String TAG = "CordovaWebViewImpl";
|
||||
|
||||
private PluginManager pluginManager;
|
||||
|
||||
protected final CordovaWebViewEngine engine;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
// Flag to track that a loadUrl timeout occurred
|
||||
private int loadUrlTimeout = 0;
|
||||
|
||||
private CordovaResourceApi resourceApi;
|
||||
private CordovaPreferences preferences;
|
||||
private CoreAndroid appPlugin;
|
||||
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private EngineClient engineClient = new EngineClient();
|
||||
private boolean hasPausedEver;
|
||||
|
||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||
String loadedUrl;
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||
|
||||
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
|
||||
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
|
||||
try {
|
||||
Class<?> webViewClass = Class.forName(className);
|
||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
|
||||
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create webview. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) {
|
||||
this.engine = cordovaWebViewEngine;
|
||||
}
|
||||
|
||||
// Convenience method for when creating programmatically (not from Config.xml).
|
||||
public void init(CordovaInterface cordova) {
|
||||
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
|
||||
}
|
||||
|
||||
@SuppressLint("Assert")
|
||||
@Override
|
||||
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.preferences = preferences;
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
|
||||
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
|
||||
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||
// This isn't enforced by the compiler, so assert here.
|
||||
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
|
||||
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||
pluginManager.init();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return cordova != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||
engine.loadUrl(url, false);
|
||||
return;
|
||||
}
|
||||
|
||||
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||
|
||||
if (recreatePlugins) {
|
||||
// Don't re-initialize on first load.
|
||||
if (loadedUrl != null) {
|
||||
appPlugin = null;
|
||||
pluginManager.init();
|
||||
}
|
||||
loadedUrl = url;
|
||||
}
|
||||
|
||||
// Create a timeout timer for loadUrl
|
||||
final int currentLoadUrlTimeout = loadUrlTimeout;
|
||||
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||
|
||||
// Timeout error method
|
||||
final Runnable loadError = new Runnable() {
|
||||
public void run() {
|
||||
stopLoading();
|
||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||
|
||||
// Handle other errors by passing them to the webview in JS
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", -6);
|
||||
data.put("description", "The connection to the server was unsuccessful.");
|
||||
data.put("url", url);
|
||||
} catch (JSONException e) {
|
||||
// Will never happen.
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
};
|
||||
|
||||
// Timeout timer method
|
||||
final Runnable timeoutCheck = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(loadUrlTimeoutValue);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// If timeout, then stop loading and handle error
|
||||
if (loadUrlTimeout == currentLoadUrlTimeout) {
|
||||
cordova.getActivity().runOnUiThread(loadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
}
|
||||
engine.loadUrl(url, _recreatePlugins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory);
|
||||
|
||||
// If clearing history
|
||||
if (clearHistory) {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
// If loading into our webview
|
||||
if (!openExternal) {
|
||||
// Make sure url is in whitelist
|
||||
if (pluginManager.shouldAllowNavigation(url)) {
|
||||
// TODO: What about params?
|
||||
// Load new URL
|
||||
loadUrlIntoView(url, true);
|
||||
} else {
|
||||
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
|
||||
}
|
||||
}
|
||||
if (!pluginManager.shouldOpenExternalUrl(url)) {
|
||||
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
Uri uri = Uri.parse(url);
|
||||
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||
} else {
|
||||
intent.setData(uri);
|
||||
}
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
LOG.d(TAG, "showing Custom View");
|
||||
// if a view already exists then immediately terminate the new one
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the view and its callback for later (to kill it properly)
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
// Add the custom view to its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.addView(view, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.CENTER));
|
||||
|
||||
// Hide the content view.
|
||||
engine.getView().setVisibility(View.GONE);
|
||||
|
||||
// Finally show the custom view container.
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
parent.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void hideCustomView() {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
if (mCustomView == null) return;
|
||||
LOG.d(TAG, "Hiding Custom View");
|
||||
|
||||
// Hide the custom view.
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
|
||||
// Remove the custom view from its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.removeView(mCustomView);
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
// Show the content view.
|
||||
engine.getView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean isCustomViewShowing() {
|
||||
return mCustomView != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void sendJavascript(String statement) {
|
||||
nativeToJsMessageQueue.addJavaScript(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginResult(PluginResult cr, String callbackId) {
|
||||
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
@Override
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return engine.getCookieManager();
|
||||
}
|
||||
@Override
|
||||
public CordovaResourceApi getResourceApi() {
|
||||
return resourceApi;
|
||||
}
|
||||
@Override
|
||||
public CordovaWebViewEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
@Override
|
||||
public View getView() {
|
||||
return engine.getView();
|
||||
}
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return engine.getView().getContext();
|
||||
}
|
||||
|
||||
private void sendJavascriptEvent(String event) {
|
||||
if (appPlugin == null) {
|
||||
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
}
|
||||
|
||||
if (appPlugin == null) {
|
||||
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||
return;
|
||||
}
|
||||
appPlugin.fireJavascriptEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
// TODO: Why are search and menu buttons handled separately?
|
||||
if (override) {
|
||||
boundKeyCodes.add(keyCode);
|
||||
} else {
|
||||
boundKeyCodes.remove(keyCode);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButtonPlumbedToJs(int keyCode) {
|
||||
return boundKeyCodes.contains(keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postMessage(String id, Object data) {
|
||||
return pluginManager.postMessage(id, data);
|
||||
}
|
||||
|
||||
// Engine method proxies:
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return engine.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
// Clear timeout flag
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void clearCache(boolean b) {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backHistory() {
|
||||
return engine.goBack();
|
||||
}
|
||||
|
||||
/////// LifeCycle methods ///////
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handlePause(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
hasPausedEver = true;
|
||||
pluginManager.onPause(keepRunning);
|
||||
sendJavascriptEvent("pause");
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleResume(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(false);
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
|
||||
// In order to match the behavior of the other platforms, we only send onResume after an
|
||||
// onPause has occurred. The resume event might still be sent if the Activity was killed
|
||||
// while waiting for the result of an external Activity once the result is obtained
|
||||
if (hasPausedEver) {
|
||||
sendJavascriptEvent("resume");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleStart() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStart();
|
||||
}
|
||||
@Override
|
||||
public void handleStop() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStop();
|
||||
}
|
||||
@Override
|
||||
public void handleDestroy() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// Cancel pending timeout timer.
|
||||
loadUrlTimeout++;
|
||||
|
||||
// Forward to plugins
|
||||
this.pluginManager.onDestroy();
|
||||
|
||||
// TODO: about:blank is a bit special (and the default URL for new frames)
|
||||
// We should use a blank data: url instead so it's more obvious
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||
engine.destroy();
|
||||
hideCustomView();
|
||||
}
|
||||
|
||||
protected class EngineClient implements CordovaWebViewEngine.Client {
|
||||
@Override
|
||||
public void clearLoadTimeoutTimer() {
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(String newUrl) {
|
||||
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
|
||||
boundKeyCodes.clear();
|
||||
pluginManager.onReset();
|
||||
pluginManager.postMessage("onPageStarted", newUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(int errorCode, String description, String failingUrl) {
|
||||
clearLoadTimeoutTimer();
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", errorCode);
|
||||
data.put("description", description);
|
||||
data.put("url", failingUrl);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinishedLoading(String url) {
|
||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||
|
||||
clearLoadTimeoutTimer();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
pluginManager.postMessage("onPageFinished", url);
|
||||
|
||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
pluginManager.postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean onDispatchKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
return true;
|
||||
} else if (isBackButton) {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
hideCustomView();
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
String eventName = null;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
eventName = "volumedownbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
eventName = "volumeupbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SEARCH:
|
||||
eventName = "searchbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
eventName = "menubutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
eventName = "backbutton";
|
||||
break;
|
||||
}
|
||||
if (eventName != null) {
|
||||
sendJavascriptEvent(eventName);
|
||||
return true;
|
||||
}
|
||||
} else if (isBackButton) {
|
||||
return engine.goBack();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationAttempt(String url) {
|
||||
// Give plugins the chance to handle the url
|
||||
if (pluginManager.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
} else if (pluginManager.shouldAllowNavigation(url)) {
|
||||
return false;
|
||||
} else if (pluginManager.shouldOpenExternalUrl(url)) {
|
||||
showWebPage(url, true, false, null);
|
||||
return true;
|
||||
}
|
||||
LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,390 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class exposes methods in Cordova that can be called from JavaScript.
|
||||
*/
|
||||
public class CoreAndroid extends CordovaPlugin {
|
||||
|
||||
public static final String PLUGIN_NAME = "CoreAndroid";
|
||||
protected static final String TAG = "CordovaApp";
|
||||
private BroadcastReceiver telephonyReceiver;
|
||||
private CallbackContext messageChannel;
|
||||
private PluginResult pendingResume;
|
||||
private final Object messageChannelLock = new Object();
|
||||
|
||||
/**
|
||||
* Send an event to be fired on the Javascript side.
|
||||
*
|
||||
* @param action The name of the event to be fired
|
||||
*/
|
||||
public void fireJavascriptEvent(String action) {
|
||||
sendEventMessage(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the context of the Command. This can then be used to do things like
|
||||
* get file paths associated with the Activity.
|
||||
*/
|
||||
@Override
|
||||
public void pluginInitialize() {
|
||||
this.initTelephonyReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param callbackContext The callback context from which we were invoked.
|
||||
* @return A PluginResult object with a status and message.
|
||||
*/
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
PluginResult.Status status = PluginResult.Status.OK;
|
||||
String result = "";
|
||||
|
||||
try {
|
||||
if (action.equals("clearCache")) {
|
||||
this.clearCache();
|
||||
}
|
||||
else if (action.equals("show")) {
|
||||
// This gets called from JavaScript onCordovaReady to show the webview.
|
||||
// I recommend we change the name of the Message as spinner/stop is not
|
||||
// indicative of what this actually does (shows the webview).
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (action.equals("loadUrl")) {
|
||||
this.loadUrl(args.getString(0), args.optJSONObject(1));
|
||||
}
|
||||
else if (action.equals("cancelLoadUrl")) {
|
||||
//this.cancelLoadUrl();
|
||||
}
|
||||
else if (action.equals("clearHistory")) {
|
||||
this.clearHistory();
|
||||
}
|
||||
else if (action.equals("backHistory")) {
|
||||
this.backHistory();
|
||||
}
|
||||
else if (action.equals("overrideButton")) {
|
||||
this.overrideButton(args.getString(0), args.getBoolean(1));
|
||||
}
|
||||
else if (action.equals("overrideBackbutton")) {
|
||||
this.overrideBackbutton(args.getBoolean(0));
|
||||
}
|
||||
else if (action.equals("exitApp")) {
|
||||
this.exitApp();
|
||||
}
|
||||
else if (action.equals("messageChannel")) {
|
||||
synchronized(messageChannelLock) {
|
||||
messageChannel = callbackContext;
|
||||
if (pendingResume != null) {
|
||||
sendEventMessage(pendingResume);
|
||||
pendingResume = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
callbackContext.sendPluginResult(new PluginResult(status, result));
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// LOCAL METHODS
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
public void clearCache() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.clearCache(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
* @param url
|
||||
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
|
||||
* @throws JSONException
|
||||
*/
|
||||
public void loadUrl(String url, JSONObject props) throws JSONException {
|
||||
LOG.d("App", "App.loadUrl("+url+","+props+")");
|
||||
int wait = 0;
|
||||
boolean openExternal = false;
|
||||
boolean clearHistory = false;
|
||||
|
||||
// If there are properties, then set them on the Activity
|
||||
HashMap<String, Object> params = new HashMap<String, Object>();
|
||||
if (props != null) {
|
||||
JSONArray keys = props.names();
|
||||
for (int i = 0; i < keys.length(); i++) {
|
||||
String key = keys.getString(i);
|
||||
if (key.equals("wait")) {
|
||||
wait = props.getInt(key);
|
||||
}
|
||||
else if (key.equalsIgnoreCase("openexternal")) {
|
||||
openExternal = props.getBoolean(key);
|
||||
}
|
||||
else if (key.equalsIgnoreCase("clearhistory")) {
|
||||
clearHistory = props.getBoolean(key);
|
||||
}
|
||||
else {
|
||||
Object value = props.get(key);
|
||||
if (value == null) {
|
||||
|
||||
}
|
||||
else if (value.getClass().equals(String.class)) {
|
||||
params.put(key, (String)value);
|
||||
}
|
||||
else if (value.getClass().equals(Boolean.class)) {
|
||||
params.put(key, (Boolean)value);
|
||||
}
|
||||
else if (value.getClass().equals(Integer.class)) {
|
||||
params.put(key, (Integer)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If wait property, then delay loading
|
||||
|
||||
if (wait > 0) {
|
||||
try {
|
||||
synchronized(this) {
|
||||
this.wait(wait);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.webView.showWebPage(url, openExternal, clearHistory, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear page history for the app.
|
||||
*/
|
||||
public void clearHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page displayed.
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
public void backHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.backHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android back button.
|
||||
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
|
||||
*
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
public void overrideBackbutton(boolean override) {
|
||||
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume buttons.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
public void overrideButton(String button, boolean override) {
|
||||
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
|
||||
if (button.equals("volumeup")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
|
||||
}
|
||||
else if (button.equals("volumedown")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
|
||||
}
|
||||
else if (button.equals("menubutton")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Android back button is overridden by the user.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isBackbuttonOverridden() {
|
||||
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the Android application.
|
||||
*/
|
||||
public void exitApp() {
|
||||
this.webView.getPluginManager().postMessage("exit", null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listen for telephony events: RINGING, OFFHOOK and IDLE
|
||||
* Send these events to all plugins using
|
||||
* CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
|
||||
*/
|
||||
private void initTelephonyReceiver() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
||||
//final CordovaInterface mycordova = this.cordova;
|
||||
this.telephonyReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
// If state has changed
|
||||
if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
|
||||
if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
|
||||
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
||||
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
||||
LOG.i(TAG, "Telephone RINGING");
|
||||
webView.getPluginManager().postMessage("telephone", "ringing");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
||||
LOG.i(TAG, "Telephone OFFHOOK");
|
||||
webView.getPluginManager().postMessage("telephone", "offhook");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
||||
LOG.i(TAG, "Telephone IDLE");
|
||||
webView.getPluginManager().postMessage("telephone", "idle");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Register the receiver
|
||||
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
|
||||
}
|
||||
|
||||
private void sendEventMessage(String action) {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("action", action);
|
||||
} catch (JSONException e) {
|
||||
LOG.e(TAG, "Failed to create event message", e);
|
||||
}
|
||||
sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
|
||||
}
|
||||
|
||||
private void sendEventMessage(PluginResult payload) {
|
||||
payload.setKeepCallback(true);
|
||||
if (messageChannel != null) {
|
||||
messageChannel.sendPluginResult(payload);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister the receiver
|
||||
*
|
||||
*/
|
||||
public void onDestroy()
|
||||
{
|
||||
webView.getContext().unregisterReceiver(this.telephonyReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to send the resume event in the case that the Activity is destroyed by the OS
|
||||
*
|
||||
* @param resumeEvent PluginResult containing the payload for the resume event to be fired
|
||||
*/
|
||||
public void sendResumeEvent(PluginResult resumeEvent) {
|
||||
// This operation must be synchronized because plugin results that trigger resume
|
||||
// events can be processed asynchronously
|
||||
synchronized(messageChannelLock) {
|
||||
if (messageChannel != null) {
|
||||
sendEventMessage(resumeEvent);
|
||||
} else {
|
||||
// Might get called before the page loads, so we need to store it until the
|
||||
// messageChannel gets created
|
||||
this.pendingResume = resumeEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||
* that read the Build Configuration.
|
||||
*
|
||||
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||
* StackOverflow. This is annoying as hell!
|
||||
*
|
||||
*/
|
||||
|
||||
public static Object getBuildConfigValue(Context ctx, String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
|
||||
Field field = clazz.getField(key);
|
||||
return field.get(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
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();
|
||||
};
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
@ -1,244 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Log to Android logging system.
|
||||
*
|
||||
* Log message can be a string or a printf formatted string with arguments.
|
||||
* See http://developer.android.com/reference/java/util/Formatter.html
|
||||
*/
|
||||
public class LOG {
|
||||
|
||||
public static final int VERBOSE = Log.VERBOSE;
|
||||
public static final int DEBUG = Log.DEBUG;
|
||||
public static final int INFO = Log.INFO;
|
||||
public static final int WARN = Log.WARN;
|
||||
public static final int ERROR = Log.ERROR;
|
||||
|
||||
// Current log level
|
||||
public static int LOGLEVEL = Log.ERROR;
|
||||
|
||||
/**
|
||||
* Set the current log level.
|
||||
*
|
||||
* @param logLevel
|
||||
*/
|
||||
public static void setLogLevel(int logLevel) {
|
||||
LOGLEVEL = logLevel;
|
||||
Log.i("CordovaLog", "Changing log level to " + logLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current log level.
|
||||
*
|
||||
* @param logLevel
|
||||
*/
|
||||
public static void setLogLevel(String logLevel) {
|
||||
if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE;
|
||||
else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG;
|
||||
else if ("INFO".equals(logLevel)) LOGLEVEL = INFO;
|
||||
else if ("WARN".equals(logLevel)) LOGLEVEL = WARN;
|
||||
else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR;
|
||||
Log.i("CordovaLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if log level will be logged
|
||||
*
|
||||
* @param logLevel
|
||||
* @return true if the parameter passed in is greater than or equal to the current log level
|
||||
*/
|
||||
public static boolean isLoggable(int logLevel) {
|
||||
return (logLevel >= LOGLEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void v(String tag, String s) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void d(String tag, String s) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void i(String tag, String s) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void w(String tag, String s) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void e(String tag, String s) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void v(String tag, String s, Throwable e) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void d(String tag, String s, Throwable e) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void i(String tag, String s, Throwable e) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param e
|
||||
*/
|
||||
public static void w(String tag, Throwable e) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void w(String tag, String s, Throwable e) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void e(String tag, String s, Throwable e) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void v(String tag, String s, Object... args) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void d(String tag, String s, Object... args) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void i(String tag, String s, Object... args) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void w(String tag, String s, Object... args) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void e(String tag, String s, Object... args) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
}
|
@ -1,542 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Holds the list of messages to be sent to the WebView.
|
||||
*/
|
||||
public class NativeToJsMessageQueue {
|
||||
private static final String LOG_TAG = "JsMessageQueue";
|
||||
|
||||
// Set this to true to force plugin results to be encoding as
|
||||
// JS instead of the custom format (useful for benchmarking).
|
||||
// Doesn't work for multipart messages.
|
||||
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
||||
|
||||
// Disable sending back native->JS messages during an exec() when the active
|
||||
// exec() is asynchronous. Set this to true when running bridge benchmarks.
|
||||
static final boolean DISABLE_EXEC_CHAINING = false;
|
||||
|
||||
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
|
||||
// This currently only chops up on message boundaries. It may be useful
|
||||
// to allow it to break up messages.
|
||||
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
|
||||
|
||||
/**
|
||||
* When true, the active listener is not fired upon enqueue. When set to false,
|
||||
* the active listener will be fired if the queue is non-empty.
|
||||
*/
|
||||
private boolean paused;
|
||||
|
||||
/**
|
||||
* The list of JavaScript statements to be sent to JavaScript.
|
||||
*/
|
||||
private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
|
||||
|
||||
/**
|
||||
* The array of listeners that can be used to send messages to JS.
|
||||
*/
|
||||
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
|
||||
|
||||
/**
|
||||
* When null, the bridge is disabled. This occurs during page transitions.
|
||||
* When disabled, all callbacks are dropped since they are assumed to be
|
||||
* relevant to the previous page.
|
||||
*/
|
||||
private BridgeMode activeBridgeMode;
|
||||
|
||||
public void addBridgeMode(BridgeMode bridgeMode) {
|
||||
bridgeModes.add(bridgeMode);
|
||||
}
|
||||
|
||||
public boolean isBridgeEnabled() {
|
||||
return activeBridgeMode != null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the bridge mode.
|
||||
*/
|
||||
public void setBridgeMode(int value) {
|
||||
if (value < -1 || value >= bridgeModes.size()) {
|
||||
LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||
} else {
|
||||
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
|
||||
if (newMode != activeBridgeMode) {
|
||||
LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||
synchronized (this) {
|
||||
activeBridgeMode = newMode;
|
||||
if (newMode != null) {
|
||||
newMode.reset();
|
||||
if (!paused && !queue.isEmpty()) {
|
||||
newMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all messages and resets to the default bridge mode.
|
||||
*/
|
||||
public void reset() {
|
||||
synchronized (this) {
|
||||
queue.clear();
|
||||
setBridgeMode(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePackedMessageLength(JsMessage message) {
|
||||
int messageLen = message.calculateEncodedLength();
|
||||
String messageLenStr = String.valueOf(messageLen);
|
||||
return messageLenStr.length() + messageLen + 1;
|
||||
}
|
||||
|
||||
private void packMessage(JsMessage message, StringBuilder sb) {
|
||||
int len = message.calculateEncodedLength();
|
||||
sb.append(len)
|
||||
.append(' ');
|
||||
message.encodeAsMessage(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and returns queued messages combined into a single string.
|
||||
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
|
||||
* Returns null if the queue is empty.
|
||||
*/
|
||||
public String popAndEncode(boolean fromOnlineEvent) {
|
||||
synchronized (this) {
|
||||
if (activeBridgeMode == null) {
|
||||
return null;
|
||||
}
|
||||
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int totalPayloadLen = 0;
|
||||
int numMessagesToSend = 0;
|
||||
for (JsMessage message : queue) {
|
||||
int messageSize = calculatePackedMessageLength(message);
|
||||
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
|
||||
break;
|
||||
}
|
||||
totalPayloadLen += messageSize;
|
||||
numMessagesToSend += 1;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(totalPayloadLen);
|
||||
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||
JsMessage message = queue.removeFirst();
|
||||
packMessage(message, sb);
|
||||
}
|
||||
|
||||
if (!queue.isEmpty()) {
|
||||
// Attach a char to indicate that there are more messages pending.
|
||||
sb.append('*');
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
||||
*/
|
||||
public String popAndEncodeAsJs() {
|
||||
synchronized (this) {
|
||||
int length = queue.size();
|
||||
if (length == 0) {
|
||||
return null;
|
||||
}
|
||||
int totalPayloadLen = 0;
|
||||
int numMessagesToSend = 0;
|
||||
for (JsMessage message : queue) {
|
||||
int messageSize = message.calculateEncodedLength() + 50; // overestimate.
|
||||
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
|
||||
break;
|
||||
}
|
||||
totalPayloadLen += messageSize;
|
||||
numMessagesToSend += 1;
|
||||
}
|
||||
boolean willSendAllMessages = numMessagesToSend == queue.size();
|
||||
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
|
||||
// Wrap each statement in a try/finally so that if one throws it does
|
||||
// not affect the next.
|
||||
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||
JsMessage message = queue.removeFirst();
|
||||
if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
|
||||
message.encodeAsJsMessage(sb);
|
||||
} else {
|
||||
sb.append("try{");
|
||||
message.encodeAsJsMessage(sb);
|
||||
sb.append("}finally{");
|
||||
}
|
||||
}
|
||||
if (!willSendAllMessages) {
|
||||
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
|
||||
}
|
||||
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
|
||||
sb.append('}');
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript statement to the list.
|
||||
*/
|
||||
public void addJavaScript(String statement) {
|
||||
enqueueMessage(new JsMessage(statement));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript statement to the list.
|
||||
*/
|
||||
public void addPluginResult(PluginResult result, String callbackId) {
|
||||
if (callbackId == null) {
|
||||
LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
|
||||
return;
|
||||
}
|
||||
// Don't send anything if there is no result and there is no need to
|
||||
// clear the callbacks.
|
||||
boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
|
||||
boolean keepCallback = result.getKeepCallback();
|
||||
if (noResult && keepCallback) {
|
||||
return;
|
||||
}
|
||||
JsMessage message = new JsMessage(result, callbackId);
|
||||
if (FORCE_ENCODE_USING_EVAL) {
|
||||
StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
|
||||
message.encodeAsJsMessage(sb);
|
||||
message = new JsMessage(sb.toString());
|
||||
}
|
||||
|
||||
enqueueMessage(message);
|
||||
}
|
||||
|
||||
private void enqueueMessage(JsMessage message) {
|
||||
synchronized (this) {
|
||||
if (activeBridgeMode == null) {
|
||||
LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
|
||||
return;
|
||||
}
|
||||
queue.add(message);
|
||||
if (!paused) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaused(boolean value) {
|
||||
if (paused && value) {
|
||||
// This should never happen. If a use-case for it comes up, we should
|
||||
// change pause to be a counter.
|
||||
LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
|
||||
}
|
||||
paused = value;
|
||||
if (!value) {
|
||||
synchronized (this) {
|
||||
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class BridgeMode {
|
||||
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||
public void reset() {}
|
||||
}
|
||||
|
||||
/** Uses JS polls for messages on a timer.. */
|
||||
public static class NoOpBridgeMode extends BridgeMode {
|
||||
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||
private final CordovaWebViewEngine engine;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||
this.engine = engine;
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
engine.loadUrl("javascript:" + js, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses online/offline events to tell the JS when to poll for messages. */
|
||||
public static class OnlineEventsBridgeMode extends BridgeMode {
|
||||
private final OnlineEventsBridgeModeDelegate delegate;
|
||||
private boolean online;
|
||||
private boolean ignoreNextFlush;
|
||||
|
||||
public interface OnlineEventsBridgeModeDelegate {
|
||||
void setNetworkAvailable(boolean value);
|
||||
void runOnUiThread(Runnable r);
|
||||
}
|
||||
|
||||
public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
online = false;
|
||||
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||
ignoreNextFlush = true;
|
||||
delegate.setNetworkAvailable(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (!queue.isEmpty()) {
|
||||
ignoreNextFlush = false;
|
||||
delegate.setNetworkAvailable(online);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Track when online/offline events are fired so that we don't fire excess events.
|
||||
@Override
|
||||
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
|
||||
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||
online = !online;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses webView.evaluateJavascript to execute messages. */
|
||||
public static class EvalBridgeMode extends BridgeMode {
|
||||
private final CordovaWebViewEngine engine;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||
this.engine = engine;
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
engine.evaluateJavascript(js, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class JsMessage {
|
||||
final String jsPayloadOrCallbackId;
|
||||
final PluginResult pluginResult;
|
||||
JsMessage(String js) {
|
||||
if (js == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
jsPayloadOrCallbackId = js;
|
||||
pluginResult = null;
|
||||
}
|
||||
JsMessage(PluginResult pluginResult, String callbackId) {
|
||||
if (callbackId == null || pluginResult == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
jsPayloadOrCallbackId = callbackId;
|
||||
this.pluginResult = pluginResult;
|
||||
}
|
||||
|
||||
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
|
||||
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||
return 1;
|
||||
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||
return 1 + pluginResult.getStrMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
int ret = 1;
|
||||
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
|
||||
int argLength = String.valueOf(length).length();
|
||||
ret += argLength + 1 + length;
|
||||
}
|
||||
return ret;
|
||||
case PluginResult.MESSAGE_TYPE_JSON:
|
||||
default:
|
||||
return pluginResult.getMessage().length();
|
||||
}
|
||||
}
|
||||
|
||||
int calculateEncodedLength() {
|
||||
if (pluginResult == null) {
|
||||
return jsPayloadOrCallbackId.length() + 1;
|
||||
}
|
||||
int statusLen = String.valueOf(pluginResult.getStatus()).length();
|
||||
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
|
||||
return ret + calculateEncodedLengthHelper(pluginResult);
|
||||
}
|
||||
|
||||
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_BOOLEAN:
|
||||
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||
sb.append('N');
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||
sb.append('n')
|
||||
.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||
sb.append('s');
|
||||
sb.append(pluginResult.getStrMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
|
||||
sb.append('S');
|
||||
sb.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
|
||||
sb.append('A');
|
||||
sb.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
sb.append('M');
|
||||
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
|
||||
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
|
||||
sb.append(' ');
|
||||
encodeAsMessageHelper(sb, multipartMessage);
|
||||
}
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_JSON:
|
||||
default:
|
||||
sb.append(pluginResult.getMessage()); // [ or {
|
||||
}
|
||||
}
|
||||
|
||||
void encodeAsMessage(StringBuilder sb) {
|
||||
if (pluginResult == null) {
|
||||
sb.append('J')
|
||||
.append(jsPayloadOrCallbackId);
|
||||
return;
|
||||
}
|
||||
int status = pluginResult.getStatus();
|
||||
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
|
||||
boolean resultOk = status == PluginResult.Status.OK.ordinal();
|
||||
boolean keepCallback = pluginResult.getKeepCallback();
|
||||
|
||||
sb.append((noResult || resultOk) ? 'S' : 'F')
|
||||
.append(keepCallback ? '1' : '0')
|
||||
.append(status)
|
||||
.append(' ')
|
||||
.append(jsPayloadOrCallbackId)
|
||||
.append(' ');
|
||||
|
||||
encodeAsMessageHelper(sb, pluginResult);
|
||||
}
|
||||
|
||||
void buildJsMessage(StringBuilder sb) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
int size = pluginResult.getMultipartMessagesSize();
|
||||
for (int i=0; i<size; i++) {
|
||||
PluginResult subresult = pluginResult.getMultipartMessage(i);
|
||||
JsMessage submessage = new JsMessage(subresult, jsPayloadOrCallbackId);
|
||||
submessage.buildJsMessage(sb);
|
||||
if (i < (size-1)) {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||
sb.append("atob('")
|
||||
.append(pluginResult.getMessage())
|
||||
.append("')");
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||
sb.append("cordova.require('cordova/base64').toArrayBuffer('")
|
||||
.append(pluginResult.getMessage())
|
||||
.append("')");
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NULL:
|
||||
sb.append("null");
|
||||
break;
|
||||
default:
|
||||
sb.append(pluginResult.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void encodeAsJsMessage(StringBuilder sb) {
|
||||
if (pluginResult == null) {
|
||||
sb.append(jsPayloadOrCallbackId);
|
||||
} else {
|
||||
int status = pluginResult.getStatus();
|
||||
boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
|
||||
sb.append("cordova.callbackFromNative('")
|
||||
.append(jsPayloadOrCallbackId)
|
||||
.append("',")
|
||||
.append(success)
|
||||
.append(",")
|
||||
.append(status)
|
||||
.append(",[");
|
||||
buildJsMessage(sb);
|
||||
sb.append("],")
|
||||
.append(pluginResult.getKeepCallback())
|
||||
.append(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
/**
|
||||
* This class provides reflective methods for permission requesting and checking so that plugins
|
||||
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
|
||||
*/
|
||||
public class PermissionHelper {
|
||||
private static final String LOG_TAG = "CordovaPermissionHelper";
|
||||
|
||||
/**
|
||||
* Requests a "dangerous" permission for the application at runtime. This is a helper method
|
||||
* alternative to cordovaInterface.requestPermission() that does not require the project to be
|
||||
* built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permission is being requested for
|
||||
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||
* along with the result of the permission request
|
||||
* @param permission The permission to be requested
|
||||
*/
|
||||
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests "dangerous" permissions for the application at runtime. This is a helper method
|
||||
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
|
||||
* built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permissions are being requested for
|
||||
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||
* along with the result of the permissions request
|
||||
* @param permissions The permissions to be requested
|
||||
*/
|
||||
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||
plugin.cordova.requestPermissions(plugin, requestCode, permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks at runtime to see if the application has been granted a permission. This is a helper
|
||||
* method alternative to cordovaInterface.hasPermission() that does not require the project to
|
||||
* be built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permission is being checked against
|
||||
* @param permission The permission to be checked
|
||||
*
|
||||
* @return True if the permission has already been granted and false otherwise
|
||||
*/
|
||||
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
|
||||
return plugin.cordova.hasPermission(permission);
|
||||
}
|
||||
|
||||
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||
// Generate the request results
|
||||
int[] requestResults = new int[permissions.length];
|
||||
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
try {
|
||||
plugin.onRequestPermissionResult(requestCode, permissions, requestResults);
|
||||
} catch (JSONException e) {
|
||||
LOG.e(LOG_TAG, "JSONException when delivering permissions results", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,526 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Debug;
|
||||
|
||||
/**
|
||||
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||
*
|
||||
* Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||
* from JavaScript.
|
||||
*/
|
||||
public class PluginManager {
|
||||
private static String TAG = "PluginManager";
|
||||
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
|
||||
|
||||
// List of service entries
|
||||
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>();
|
||||
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>();
|
||||
|
||||
private final CordovaInterface ctx;
|
||||
private final CordovaWebView app;
|
||||
private boolean isInitialized;
|
||||
|
||||
private CordovaPlugin permissionRequester;
|
||||
|
||||
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||
this.ctx = cordova;
|
||||
this.app = cordovaWebView;
|
||||
setPluginEntries(pluginEntries);
|
||||
}
|
||||
|
||||
public Collection<PluginEntry> getPluginEntries() {
|
||||
return entryMap.values();
|
||||
}
|
||||
|
||||
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||
if (isInitialized) {
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
entryMap.clear();
|
||||
}
|
||||
for (PluginEntry entry : pluginEntries) {
|
||||
addService(entry);
|
||||
}
|
||||
if (isInitialized) {
|
||||
startupPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init when loading a new HTML page into webview.
|
||||
*/
|
||||
public void init() {
|
||||
LOG.d(TAG, "init()");
|
||||
isInitialized = true;
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
this.startupPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create plugins objects that have onload set.
|
||||
*/
|
||||
private void startupPlugins() {
|
||||
for (PluginEntry entry : entryMap.values()) {
|
||||
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
|
||||
// When iterating plugins.
|
||||
if (entry.onload) {
|
||||
getPlugin(entry.service);
|
||||
} else {
|
||||
pluginMap.put(entry.service, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a request for execution and fulfills it by finding the appropriate
|
||||
* Java class and calling it's execute method.
|
||||
*
|
||||
* PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
|
||||
* string is returned that will indicate if any errors have occurred when trying to find
|
||||
* or execute the class denoted by the clazz argument.
|
||||
*
|
||||
* @param service String containing the service to run
|
||||
* @param action String containing the action that the class is supposed to perform. This is
|
||||
* passed to the plugin execute method and it is up to the plugin developer
|
||||
* how to deal with it.
|
||||
* @param callbackId String containing the id of the callback that is execute in JavaScript if
|
||||
* this is an async plugin call.
|
||||
* @param rawArgs An Array literal string containing any arguments needed in the
|
||||
* plugin execute method.
|
||||
*/
|
||||
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||
CordovaPlugin plugin = getPlugin(service);
|
||||
if (plugin == null) {
|
||||
LOG.d(TAG, "exec() call to unknown plugin: " + service);
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
|
||||
app.sendPluginResult(cr, callbackId);
|
||||
return;
|
||||
}
|
||||
CallbackContext callbackContext = new CallbackContext(callbackId, app);
|
||||
try {
|
||||
long pluginStartTime = System.currentTimeMillis();
|
||||
boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
|
||||
long duration = System.currentTimeMillis() - pluginStartTime;
|
||||
|
||||
if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
|
||||
LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
|
||||
}
|
||||
if (!wasValidAction) {
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
|
||||
callbackContext.sendPluginResult(cr);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
||||
callbackContext.sendPluginResult(cr);
|
||||
} catch (Exception e) {
|
||||
LOG.e(TAG, "Uncaught exception from plugin", e);
|
||||
callbackContext.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin object that implements the service.
|
||||
* If the plugin object does not already exist, then create it.
|
||||
* If the service doesn't exist, then return null.
|
||||
*
|
||||
* @param service The name of the service.
|
||||
* @return CordovaPlugin or null
|
||||
*/
|
||||
public CordovaPlugin getPlugin(String service) {
|
||||
CordovaPlugin ret = pluginMap.get(service);
|
||||
if (ret == null) {
|
||||
PluginEntry pe = entryMap.get(service);
|
||||
if (pe == null) {
|
||||
return null;
|
||||
}
|
||||
if (pe.plugin != null) {
|
||||
ret = pe.plugin;
|
||||
} else {
|
||||
ret = instantiatePlugin(pe.pluginClass);
|
||||
}
|
||||
ret.privateInitialize(service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(service, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin class that implements a service to the service entry table.
|
||||
* This does not create the plugin object instance.
|
||||
*
|
||||
* @param service The service name
|
||||
* @param className The plugin class name
|
||||
*/
|
||||
public void addService(String service, String className) {
|
||||
PluginEntry entry = new PluginEntry(service, className, false);
|
||||
this.addService(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin class that implements a service to the service entry table.
|
||||
* This does not create the plugin object instance.
|
||||
*
|
||||
* @param entry The plugin entry
|
||||
*/
|
||||
public void addService(PluginEntry entry) {
|
||||
this.entryMap.put(entry.service, entry);
|
||||
if (entry.plugin != null) {
|
||||
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(entry.service, entry.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onPause(boolean multitasking) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onPause(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system received an HTTP authentication request. Plugins can use
|
||||
* the supplied HttpAuthHandler to process this auth challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||
* @param host The host requiring authentication
|
||||
* @param realm The realm for which authentication is required
|
||||
*
|
||||
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when he system received an SSL client certificate request. Plugin can use
|
||||
* the supplied ClientCertRequest to process this certificate challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request The client certificate request
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onResume(boolean multitasking) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onResume(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
public void onStart() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
public void onStop() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object to stop propagation or null
|
||||
*/
|
||||
public Object postMessage(String id, Object data) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Object obj = plugin.onMessage(id, data);
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.onMessage(id, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent.
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to request an external resource.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the resource to load,
|
||||
* false to block the resource.
|
||||
*/
|
||||
public boolean shouldAllowRequest(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowRequest(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
|
||||
return true;
|
||||
}
|
||||
// TalkBack requires this, so allow it by default.
|
||||
if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("file://")) {
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to change the URL of the loaded content.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the navigation,
|
||||
* false to block the navigation.
|
||||
*/
|
||||
public boolean shouldAllowNavigation(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowNavigation(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://") || url.startsWith("about:blank");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the webview is requesting the exec() bridge be enabled.
|
||||
*/
|
||||
public boolean shouldAllowBridgeAccess(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowBridgeAccess(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going not going to navigate, but may launch
|
||||
* an Intent for an URL.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the URL to launch an intent,
|
||||
* false to block the intent.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default policy:
|
||||
// External URLs are not allowed
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the URL of the webview changes.
|
||||
*
|
||||
* @param url The URL that is being changed to.
|
||||
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
|
||||
*/
|
||||
public boolean onOverrideUrlLoading(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the app navigates or refreshes.
|
||||
*/
|
||||
public void onReset() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri remapUri(Uri uri) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Uri ret = plugin.remapUri(uri);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plugin based on class name.
|
||||
*/
|
||||
private CordovaPlugin instantiatePlugin(String className) {
|
||||
CordovaPlugin ret = null;
|
||||
try {
|
||||
Class<?> c = null;
|
||||
if ((className != null) && !("".equals(className))) {
|
||||
c = Class.forName(className);
|
||||
}
|
||||
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
|
||||
ret = (CordovaPlugin) c.newInstance();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Error adding plugin " + className + ".");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle onSaveInstanceState() {
|
||||
Bundle state = new Bundle();
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Bundle pluginState = plugin.onSaveInstanceState();
|
||||
if(pluginState != null) {
|
||||
state.putBundle(plugin.getServiceName(), pluginState);
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import java.util.Arrays;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebStorage;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.apache.cordova.CordovaDialogsHelper;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
/**
|
||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||
* to but different than CordovaWebViewClient.
|
||||
*/
|
||||
public class SystemWebChromeClient extends WebChromeClient {
|
||||
|
||||
private static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private static final String LOG_TAG = "SystemWebChromeClient";
|
||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||
protected final SystemWebViewEngine parentEngine;
|
||||
|
||||
// the video progress view
|
||||
private View mVideoProgressView;
|
||||
|
||||
private CordovaDialogsHelper dialogsHelper;
|
||||
private Context appContext;
|
||||
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
private View mCustomView;
|
||||
|
||||
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
|
||||
this.parentEngine = parentEngine;
|
||||
appContext = parentEngine.webView.getContext();
|
||||
dialogsHelper = new CordovaDialogsHelper(appContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a javascript alert dialog.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
|
||||
@Override public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a confirm dialog to the user.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a prompt dialog to the user.
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
||||
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||
if (handledRet != null) {
|
||||
result.confirm(handledRet);
|
||||
} else {
|
||||
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm(value);
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle database quota exceeded notification.
|
||||
*/
|
||||
@Override
|
||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||
{
|
||||
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,367 +0,0 @@
|
||||
/*
|
||||
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(Build.VERSION_CODES.LOLLIPOP)
|
||||
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.
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
|
||||
@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 "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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.apache.cordova.CordovaBridge;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPreferences;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
import org.apache.cordova.ICordovaCookieManager;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.apache.cordova.NativeToJsMessageQueue;
|
||||
import org.apache.cordova.PluginManager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
|
||||
/**
|
||||
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
|
||||
* We make the Engine separate from the actual View so that:
|
||||
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
|
||||
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
|
||||
* B) Separating the actual View from the Engine makes API surfaces smaller.
|
||||
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
|
||||
*/
|
||||
public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
public static final String TAG = "SystemWebViewEngine";
|
||||
|
||||
protected final SystemWebView webView;
|
||||
protected final SystemCookieManager cookieManager;
|
||||
protected CordovaPreferences preferences;
|
||||
protected CordovaBridge bridge;
|
||||
protected CordovaWebViewEngine.Client client;
|
||||
protected CordovaWebView parentWebView;
|
||||
protected CordovaInterface cordova;
|
||||
protected PluginManager pluginManager;
|
||||
protected CordovaResourceApi resourceApi;
|
||||
protected NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
/** Used when created via reflection. */
|
||||
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||
this(new SystemWebView(context), preferences);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView) {
|
||||
this(webView, null);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||
this.preferences = preferences;
|
||||
this.webView = webView;
|
||||
cookieManager = new SystemCookieManager(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// Needed when prefs are not passed by the constructor
|
||||
if (preferences == null) {
|
||||
preferences = parentWebView.getPreferences();
|
||||
}
|
||||
this.parentWebView = parentWebView;
|
||||
this.cordova = cordova;
|
||||
this.client = client;
|
||||
this.resourceApi = resourceApi;
|
||||
this.pluginManager = pluginManager;
|
||||
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
|
||||
webView.init(this, cordova);
|
||||
|
||||
initWebViewSettings();
|
||||
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||
@Override
|
||||
public void setNetworkAvailable(boolean value) {
|
||||
//sometimes this can be called after calling webview.destroy() on destroy()
|
||||
//thus resulting in a NullPointerException
|
||||
if(webView!=null) {
|
||||
webView.setNetworkAvailable(value);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void runOnUiThread(Runnable r) {
|
||||
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
|
||||
}
|
||||
}));
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
|
||||
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
||||
exposeJsInterface(webView, bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaWebView getCordovaWebView() {
|
||||
return parentWebView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return webView;
|
||||
}
|
||||
|
||||
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initWebViewSettings() {
|
||||
webView.setInitialScale(0);
|
||||
webView.setVerticalScrollBarEnabled(false);
|
||||
// Enable JavaScript
|
||||
final WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||
|
||||
String manufacturer = android.os.Build.MANUFACTURER;
|
||||
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||
|
||||
//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
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
private void enableRemoteDebugging() {
|
||||
try {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Yeah, we know. It'd be great if lint was just a little smarter.
|
||||
@SuppressLint("AddJavascriptInterface")
|
||||
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
|
||||
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
|
||||
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return webView.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
webView.stopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
webView.clearCache(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return webView.canGoBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
@Override
|
||||
public boolean goBack() {
|
||||
// Check webview first to see if there is a history
|
||||
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaused(boolean value) {
|
||||
if (value) {
|
||||
webView.onPause();
|
||||
webView.pauseTimers();
|
||||
} else {
|
||||
webView.onResume();
|
||||
webView.resumeTimers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
webView.chromeClient.destroyLastDialog();
|
||||
webView.destroy();
|
||||
// unregister the receiver
|
||||
if (receiver != null) {
|
||||
try {
|
||||
webView.getContext().unregisterReceiver(receiver);
|
||||
} catch (Exception e) {
|
||||
LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateJavascript(String js, ValueCallback<String> callback) {
|
||||
webView.evaluateJavascript(js, callback);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"prepare_queue": {
|
||||
"installed": [],
|
||||
"uninstalled": []
|
||||
},
|
||||
"config_munge": {
|
||||
"files": {
|
||||
"res/xml/config.xml": {
|
||||
"parents": {
|
||||
"/*": [
|
||||
{
|
||||
"xml": "<feature name=\"Whitelist\"><param name=\"android-package\" value=\"org.apache.cordova.whitelist.WhitelistPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"installed_plugins": {
|
||||
"cordova-plugin-whitelist": {
|
||||
"PACKAGE_NAME": "com.lalbornoz.MiRCART"
|
||||
}
|
||||
},
|
||||
"dependent_plugins": {},
|
||||
"modules": [],
|
||||
"plugin_metadata": {
|
||||
"cordova-plugin-whitelist": "1.3.3"
|
||||
}
|
||||
}
|
@ -1,329 +0,0 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
// Allow plugins to declare Maven dependencies via build-extras.gradle.
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral();
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.1.0'
|
||||
}
|
||||
|
||||
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
|
||||
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
|
||||
ext {
|
||||
apply from: '../CordovaLib/cordova.gradle'
|
||||
// The value for android.compileSdkVersion.
|
||||
if (!project.hasProperty('cdvCompileSdkVersion')) {
|
||||
cdvCompileSdkVersion = null;
|
||||
}
|
||||
// The value for android.buildToolsVersion.
|
||||
if (!project.hasProperty('cdvBuildToolsVersion')) {
|
||||
cdvBuildToolsVersion = null;
|
||||
}
|
||||
// Sets the versionCode to the given value.
|
||||
if (!project.hasProperty('cdvVersionCode')) {
|
||||
cdvVersionCode = null
|
||||
}
|
||||
// Sets the minSdkVersion to the given value.
|
||||
if (!project.hasProperty('cdvMinSdkVersion')) {
|
||||
cdvMinSdkVersion = null
|
||||
}
|
||||
// Whether to build architecture-specific APKs.
|
||||
if (!project.hasProperty('cdvBuildMultipleApks')) {
|
||||
cdvBuildMultipleApks = null
|
||||
}
|
||||
// Whether to append a 0 "abi digit" to versionCode when only a single APK is build
|
||||
if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
|
||||
cdvVersionCodeForceAbiDigit = null
|
||||
}
|
||||
// .properties files to use for release signing.
|
||||
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
|
||||
cdvReleaseSigningPropertiesFile = null
|
||||
}
|
||||
// .properties files to use for debug signing.
|
||||
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
|
||||
cdvDebugSigningPropertiesFile = null
|
||||
}
|
||||
// Set by build.js script.
|
||||
if (!project.hasProperty('cdvBuildArch')) {
|
||||
cdvBuildArch = null
|
||||
}
|
||||
|
||||
// Plugin gradle extensions can append to this to have code run at the end.
|
||||
cdvPluginPostBuildExtras = []
|
||||
}
|
||||
|
||||
// PLUGIN GRADLE EXTENSIONS START
|
||||
// PLUGIN GRADLE EXTENSIONS END
|
||||
|
||||
def hasBuildExtras1 = file('build-extras.gradle').exists()
|
||||
if (hasBuildExtras1) {
|
||||
apply from: 'build-extras.gradle'
|
||||
}
|
||||
|
||||
def hasBuildExtras2 = file('../build-extras.gradle').exists()
|
||||
if (hasBuildExtras2) {
|
||||
apply from: '../build-extras.gradle'
|
||||
}
|
||||
|
||||
// Set property defaults after extension .gradle files.
|
||||
if (ext.cdvCompileSdkVersion == null) {
|
||||
ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
//ext.cdvCompileSdkVersion = project.ext.defaultCompileSdkVersion
|
||||
}
|
||||
if (ext.cdvBuildToolsVersion == null) {
|
||||
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
//ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
|
||||
}
|
||||
if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) {
|
||||
ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties'
|
||||
}
|
||||
if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.properties').exists()) {
|
||||
ext.cdvReleaseSigningPropertiesFile = '../release-signing.properties'
|
||||
}
|
||||
|
||||
// Cast to appropriate types.
|
||||
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
|
||||
ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
|
||||
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? defaultMinSdkVersion : Integer.parseInt('' + cdvMinSdkVersion)
|
||||
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
|
||||
|
||||
def computeBuildTargetName(debugBuild) {
|
||||
def ret = 'assemble'
|
||||
if (cdvBuildMultipleApks && cdvBuildArch) {
|
||||
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
|
||||
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
|
||||
}
|
||||
return ret + (debugBuild ? 'Debug' : 'Release')
|
||||
}
|
||||
|
||||
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
|
||||
task cdvBuildDebug
|
||||
cdvBuildDebug.dependsOn {
|
||||
return computeBuildTargetName(true)
|
||||
}
|
||||
|
||||
task cdvBuildRelease
|
||||
cdvBuildRelease.dependsOn {
|
||||
return computeBuildTargetName(false)
|
||||
}
|
||||
|
||||
task cdvPrintProps << {
|
||||
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
|
||||
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
|
||||
println('cdvVersionCode=' + cdvVersionCode)
|
||||
println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
|
||||
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
|
||||
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
|
||||
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
|
||||
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
|
||||
println('cdvBuildArch=' + cdvBuildArch)
|
||||
println('computedVersionCode=' + android.defaultConfig.versionCode)
|
||||
android.productFlavors.each { flavor ->
|
||||
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
|
||||
applicationId privateHelpers.extractStringFromManifest("package")
|
||||
|
||||
if (cdvMinSdkVersion != null) {
|
||||
minSdkVersion cdvMinSdkVersion
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false;
|
||||
}
|
||||
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
|
||||
// This code exists for Crosswalk and other Native APIs.
|
||||
// By default, we multiply the existing version code in the
|
||||
// Android Manifest by 10 and add a number for each architecture.
|
||||
// If you are not using Crosswalk or SQLite, you can
|
||||
// ignore this chunk of code, and your version codes will be respected.
|
||||
|
||||
if (Boolean.valueOf(cdvBuildMultipleApks)) {
|
||||
flavorDimensions "default"
|
||||
|
||||
productFlavors {
|
||||
armeabi {
|
||||
versionCode defaultConfig.versionCode*10 + 1
|
||||
ndk {
|
||||
abiFilters = ["armeabi"]
|
||||
}
|
||||
}
|
||||
armv7 {
|
||||
versionCode defaultConfig.versionCode*10 + 2
|
||||
ndk {
|
||||
abiFilters = ["armeabi-v7a"]
|
||||
}
|
||||
}
|
||||
arm64 {
|
||||
versionCode defaultConfig.versionCode*10 + 3
|
||||
ndk {
|
||||
abiFilters = ["arm64-v8a"]
|
||||
}
|
||||
}
|
||||
x86 {
|
||||
versionCode defaultConfig.versionCode*10 + 4
|
||||
ndk {
|
||||
abiFilters = ["x86"]
|
||||
}
|
||||
}
|
||||
x86_64 {
|
||||
versionCode defaultConfig.versionCode*10 + 5
|
||||
ndk {
|
||||
abiFilters = ["x86_64"]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
|
||||
// This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
|
||||
defaultConfig {
|
||||
versionCode defaultConfig.versionCode*10
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
if (cdvReleaseSigningPropertiesFile) {
|
||||
signingConfigs {
|
||||
release {
|
||||
// These must be set or Gradle will complain (even if they are overridden).
|
||||
keyAlias = ""
|
||||
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
|
||||
storeFile = null
|
||||
storePassword = "__unset"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
|
||||
}
|
||||
if (cdvDebugSigningPropertiesFile) {
|
||||
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* WARNING: Cordova Lib and platform scripts do management inside of this code here,
|
||||
* if you are adding the dependencies manually, do so outside the comments, otherwise
|
||||
* the Cordova tools will overwrite them
|
||||
*/
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
// SUB-PROJECT DEPENDENCIES START
|
||||
implementation(project(path: ":CordovaLib"))
|
||||
// SUB-PROJECT DEPENDENCIES END
|
||||
}
|
||||
|
||||
def promptForReleaseKeyPassword() {
|
||||
if (!cdvReleaseSigningPropertiesFile) {
|
||||
return;
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
|
||||
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
|
||||
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
|
||||
}
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
taskGraph.getAllTasks().each() { task ->
|
||||
if(['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
|
||||
promptForReleaseKeyPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addSigningProps(propsFilePath, signingConfig) {
|
||||
def propsFile = file(propsFilePath)
|
||||
def props = new Properties()
|
||||
propsFile.withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
|
||||
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||
if (!storeFile.isAbsolute()) {
|
||||
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
|
||||
}
|
||||
if (!storeFile.exists()) {
|
||||
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
|
||||
}
|
||||
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
|
||||
signingConfig.storeFile = storeFile
|
||||
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
|
||||
def storeType = props.get('storeType', props.get('key.store.type', ''))
|
||||
if (!storeType) {
|
||||
def filename = storeFile.getName().toLowerCase();
|
||||
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||
storeType = 'pkcs12'
|
||||
} else {
|
||||
storeType = signingConfig.storeType // "jks"
|
||||
}
|
||||
}
|
||||
signingConfig.storeType = storeType
|
||||
}
|
||||
|
||||
for (def func : cdvPluginPostBuildExtras) {
|
||||
func()
|
||||
}
|
||||
|
||||
// This can be defined within build-extras.gradle as:
|
||||
// ext.postBuildExtras = { ... code here ... }
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest android:hardwareAccelerated="true" android:versionCode="10010" android:versionName="1.1.0" package="com.lalbornoz.MiRCART" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
|
||||
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter android:label="@string/launcher_name">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
|
||||
</manifest>
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Background mIRC colours
|
||||
*/
|
||||
.ba {background-color: #FFFFFF}
|
||||
.bb {background-color: #000000}
|
||||
.bc {background-color: #00007F}
|
||||
.bd {background-color: #009300}
|
||||
.be {background-color: #FF0000}
|
||||
.bf {background-color: #7f0000}
|
||||
.bg {background-color: #9C009C}
|
||||
.bh {background-color: #FC7F00}
|
||||
.bi {background-color: #FFFF00}
|
||||
.bj {background-color: #00FC00}
|
||||
.bk {background-color: #009393}
|
||||
.bl {background-color: #00FFFF}
|
||||
.bm {background-color: #0000FC}
|
||||
.bn {background-color: #FF00FF}
|
||||
.bo {background-color: #7F7F7F}
|
||||
.bp {background-color: #D2D2D2}
|
||||
|
||||
/*
|
||||
* Foreground mIRC colours
|
||||
*/
|
||||
.fa {color: #FFFFFF}
|
||||
.fb {color: #000000}
|
||||
.fc {color: #00007F}
|
||||
.fd {color: #009300}
|
||||
.fe {color: #FF0000}
|
||||
.ff {color: #7F0000}
|
||||
.fg {color: #9C009C}
|
||||
.fh {color: #FC7F00}
|
||||
.fi {color: #FFFF00}
|
||||
.fj {color: #00FC00}
|
||||
.fk {color: #009393}
|
||||
.fl {color: #00FFFF}
|
||||
.fm {color: #0000FC}
|
||||
.fn {color: #FF00FF}
|
||||
.fo {color: #7F7F7F}
|
||||
.fp {color: #D2D2D2}
|
||||
|
||||
/*
|
||||
* vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
font-style: normal;
|
||||
src: url('../fonts/fsex300-webfont.eot');
|
||||
src: url('../fonts/fsex300-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/fsex300-webfont.woff') format('woff'),
|
||||
url('../fonts/fsex300-webfont.ttf') format('truetype'),
|
||||
url('../fonts/fsex300-webfont.svg#FixedsysExcelsior301Regular') format('svg');
|
||||
}
|
||||
|
||||
/*
|
||||
* vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
*/
|
@ -1,48 +0,0 @@
|
||||
body {
|
||||
background: #000000;
|
||||
background-image: url('../images/tile.jpg');
|
||||
color: rgb(255,255,0);
|
||||
font-family: times new roman;
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
/* {{{ DIV id styles */
|
||||
div#box {
|
||||
background: rgba(0,0,100,0.5);
|
||||
border: 3px solid #0000ff;
|
||||
border-spacing: 0px;
|
||||
color: #00ffff!important;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
font-size: 12pt!important;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
}
|
||||
div#box_wrapper {
|
||||
margin: 0 auto;
|
||||
width: 50%;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ Element styles */
|
||||
h1 {
|
||||
color: #ff00ff!important;
|
||||
font-size: 42px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
h2 {
|
||||
color: #00ffff!important;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
pre {
|
||||
color: white;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/*
|
||||
* vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
*/
|
@ -1,289 +0,0 @@
|
||||
|
||||
/* {{{ BODY.grid styles */
|
||||
body.grid div {
|
||||
border-left: 1px solid #444444;
|
||||
}
|
||||
body.grid span {
|
||||
border-bottom: 1px solid #444444;
|
||||
border-right: 1px solid #444444;
|
||||
}
|
||||
body.grid .tool {
|
||||
border: 1px solid #444444;
|
||||
}
|
||||
body.grid #brush_wrapper > div:first-child,
|
||||
body.grid #canvas_wrapper > div:first-child,
|
||||
body.grid #letters_wrapper > div:first-child,
|
||||
body.grid #palette_wrapper > div:first-child {
|
||||
border-top: 1px solid #444444;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ .initial styles */
|
||||
.initial #add_custom_el,
|
||||
.initial #doc_el,
|
||||
.initial #export_button,
|
||||
.initial #format_el,
|
||||
.initial #grid_el,
|
||||
.initial #import_textarea,
|
||||
.initial #load_el,
|
||||
.initial #save_el,
|
||||
.initial #vertical_checkbox {
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ .selector_el styles */
|
||||
.selector_el {
|
||||
border: 1px dashed white !important;
|
||||
left: -999px;
|
||||
margin-top: -1px;
|
||||
padding-top: 1px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -999px;
|
||||
}
|
||||
.selector_el.creating div {
|
||||
display: none; }
|
||||
.selector_el.dragging {
|
||||
color: #00FF00;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ .tool styles */
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tool.focused {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.tool.locked.focused {
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
.tool.radio {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.tool.radio.focused {
|
||||
background-color: #6D6D6D;
|
||||
box-shadow: none;
|
||||
color: black;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ .vertical styles */
|
||||
.vertical .wrapper, .vertical .block {
|
||||
float: left;
|
||||
}
|
||||
.vertical #brush_container {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
.vertical #canvas_wrapper,
|
||||
.vertical #canvas_wrapper div,
|
||||
.vertical #palette_wrapper,
|
||||
.vertical #palette_wrapper {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.vertical #secret_wrapper {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.vertical #secret_wrapper span {
|
||||
clear: both;
|
||||
float: left;
|
||||
}
|
||||
.vertical #tools_block {
|
||||
min-width: 100%;
|
||||
}
|
||||
.vertical #tools_wrapper,
|
||||
.vertical #ui_wrapper {
|
||||
clear: none;
|
||||
float: left;
|
||||
width: 320px;
|
||||
}
|
||||
.vertical #workspace_wrapper {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: auto;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Class & id styles (simple) */
|
||||
.bucket #canvas_wrapper { cursor: url(../images/bucket.png) 3 15, auto; }
|
||||
.dropper #canvas_wrapper { cursor: url(../images/dropper.gif) 0 15, auto; }
|
||||
.faded { color: #404040; }
|
||||
.hidden { visibility: hidden; }
|
||||
.loading .vertical #ui_wrapper { clear: none }
|
||||
#experimental_palette_toggle.focused { box-shadow: none; }
|
||||
#import_wrapper { display: none; }
|
||||
#tools_block > * { cursor: crosshair; }
|
||||
#workspace_wrapper { width: 100%; }
|
||||
/* }}} */
|
||||
/* {{{ Class styles (complex) */
|
||||
.ba.focused {
|
||||
box-shadow: inset 0 0px 2px #000, inset 0 0px 2px black;
|
||||
border-color: black;
|
||||
}
|
||||
.block {
|
||||
background-color: black;
|
||||
float: left;
|
||||
height: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
width: auto;
|
||||
}
|
||||
.block {
|
||||
padding: 4px;
|
||||
}
|
||||
.block:nth-child(n+2) {
|
||||
padding-left: 30px;
|
||||
}
|
||||
.close {
|
||||
background: black;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
z-index: 2;
|
||||
}
|
||||
.custom {
|
||||
float: left;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.ed {
|
||||
color: white;
|
||||
}
|
||||
.ed.focused {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.faba.focused, .fbba.focused, .fcba.focused, .fdba.focused, .feba.focused, .ffba.focused, .fgba.focused, .fhba.focused,
|
||||
.fiba.focused, .fjba.focused, .fkba.focused, .flba.focused, .fmba.focused, .fnba.focused, .foba.focused, .fpba.focused {
|
||||
box-shadow: inset 1px 0 2px #888888, inset -1px 0 2px #888888, inset 0 1px 2px #888888, inset 0 -1px 2px #888888;
|
||||
}
|
||||
.focused {
|
||||
box-shadow: inset 1px 0 2px white, inset -1px 0 2px white, inset 0 1px 2px white, inset 0 -1px 2px white;
|
||||
border-color: white;
|
||||
}
|
||||
.locked {
|
||||
border-bottom: 1px solid;
|
||||
color: #bbbbbb;
|
||||
text-decoration: none;
|
||||
}
|
||||
.transparent {
|
||||
background-color: transparent;
|
||||
background-image: url(../images/gray-dither.gif);
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
@media (-webkit-min-device-pixel-ratio: 2) {
|
||||
.transparent {
|
||||
background-size: 4px 4px;
|
||||
}
|
||||
}
|
||||
.wrapper {
|
||||
background-color: black;
|
||||
cursor: crosshair;
|
||||
float: left;
|
||||
height: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
white-space: pre-wrap;
|
||||
width: auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ Element styles */
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
a:link, a:visited {
|
||||
color: #6B6760;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
body {
|
||||
background-color: black !important;
|
||||
transition: 0.1s linear;
|
||||
}
|
||||
body.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
line-height: 15px;
|
||||
min-width: 8px;
|
||||
}
|
||||
textarea, input[type=text], body {
|
||||
color: #6D6B6D;
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
line-height: 11pt;
|
||||
font-size: 12pt;
|
||||
font-weight: 100;
|
||||
margin: 0;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ Id styles (complex) */
|
||||
#brush_wrapper {
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-bottom: 13px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#brush_wrapper, #letters_wrapper {
|
||||
cursor: crosshair;
|
||||
}
|
||||
#canvas_wrapper {
|
||||
box-shadow: 0 0 2px rgba(255,255,255,0.3);
|
||||
margin: 3px;
|
||||
white-space: pre;
|
||||
}
|
||||
#cursor_input {
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 30px;
|
||||
}
|
||||
#import_textarea {
|
||||
background: #001100;
|
||||
border: 1px solid #333333;
|
||||
color: #00FF00;
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
font-size: 12pt;
|
||||
height: 300px;
|
||||
outline: 0;
|
||||
width: 37vw;
|
||||
}
|
||||
#letters_wrapper {
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
#secret_wrapper {
|
||||
clear: right;
|
||||
float: left;
|
||||
}
|
||||
#secret_wrapper span {
|
||||
float: left;
|
||||
}
|
||||
#textarea_mode {
|
||||
float: left;
|
||||
padding: 4px;
|
||||
}
|
||||
#ui_wrapper {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 100vw;
|
||||
}
|
||||
#ui_wrapper .block {
|
||||
width: 100px;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/*
|
||||
* vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
*/
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<!-- {{{ HEAD -->
|
||||
<head>
|
||||
<link charset="utf-8" href="../css/fonts.css" rel="stylesheet" type="text/css" />
|
||||
<link charset="utf-8" href="../css/help.css" rel="stylesheet" type="text/css" />
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
<title>MiRCART v1.1.0 documentation</title>
|
||||
</head>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ BODY -->
|
||||
<body>
|
||||
<div id="box_wrapper">
|
||||
<div id="box">
|
||||
<h1>MiRCART documentation</h1>
|
||||
|
||||
<h2>Tips on using the keyboard</h2>
|
||||
<pre>
|
||||
[ Decrease brush size
|
||||
] Increase brush size
|
||||
<Alt> + <LMB> on canvas Fill brush with sampled color
|
||||
<Alt> + <Shift> + <LMB> on canvas Copy canvas to brush
|
||||
<Ctrl> + <LMB> on brush Erase brush cell
|
||||
<Ctrl> + <LMB> on canvas Draw with background color
|
||||
<Shift> + <LMB> on canvas Draw line from last position
|
||||
<Shift> + <LMB> on character box Switch to next character set
|
||||
<Shift> + <LMB> on stored brush Delete stored brush
|
||||
<LMB> on brush Fill brush cell
|
||||
<RMB> on brush Erase brush cell
|
||||
<RMB> on palette Set background color (when drawing with a letter)
|
||||
</pre>
|
||||
|
||||
<h2>Notate bene</h2>
|
||||
<p>
|
||||
Characters not forming part of the initial set in the character box beneath the colour palette
|
||||
encode to, assuming UTF-8, 2-3 bytes in total vs. one (1) byte for those part of the initial
|
||||
set (e.g. 0-9, A-Z, a-z, etc.)<br />
|
||||
<br />
|
||||
Internet Explorer is explicitly unsupported.<br />
|
||||
<br />
|
||||
Repeating patterns of alternating back- and foreground colours, and vice versa, are encoded
|
||||
using a single (1) byte in mIRC format (video reverse) and as such save space, especially
|
||||
with large numbers of repetitions or alternations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<!-- }}} -->
|
||||
</html>
|
||||
|
||||
<!--
|
||||
vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
-->
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.5 KiB |
@ -1,89 +0,0 @@
|
||||
|
||||
var dragging = false
|
||||
var drawing = false
|
||||
var erasing = false
|
||||
var selecting = false
|
||||
var filling = false
|
||||
var changed = false
|
||||
var transforming = false
|
||||
var focused
|
||||
|
||||
var canvas, tools, palette, controls, brush, mode
|
||||
var current_tool, current_filetool, current_canvas
|
||||
var mouse = { x: 0, y: 0 }
|
||||
|
||||
function init () {
|
||||
build()
|
||||
bind()
|
||||
}
|
||||
function build () {
|
||||
canvas.append(canvas_wrapper)
|
||||
brush.append(brush_wrapper)
|
||||
palette.append(palette_wrapper)
|
||||
letters.append(letters_wrapper)
|
||||
letters.repaint("Basic Latin")
|
||||
|
||||
controls.circle.focus()
|
||||
|
||||
brush.bg = colors.red
|
||||
brush.generate()
|
||||
brush.build()
|
||||
|
||||
// controls.grid.use()
|
||||
canvas.resize_wrapper()
|
||||
}
|
||||
function bind () {
|
||||
canvas.bind()
|
||||
palette.bind()
|
||||
letters.bind()
|
||||
brush.bind()
|
||||
controls.bind()
|
||||
keys.bind()
|
||||
clipboard.bind()
|
||||
|
||||
window.addEventListener('mouseup', function(e){
|
||||
dragging = erasing = false
|
||||
|
||||
var ae = document.activeElement
|
||||
|
||||
if (ae !== import_textarea) {
|
||||
cursor_input.focus()
|
||||
}
|
||||
|
||||
if (selecting) {
|
||||
selection.up(e)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.up(e)
|
||||
}
|
||||
})
|
||||
window.addEventListener("touchend", function(){
|
||||
if (current_tool.name === "text") {
|
||||
cursor_input.focus()
|
||||
}
|
||||
dragging = false
|
||||
})
|
||||
|
||||
window.addEventListener('mousedown', function(e){
|
||||
// cursor_input.focus()
|
||||
})
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
cursor_input.focus()
|
||||
document.body.classList.remove('loading')
|
||||
})
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
// if (changed && !in_iframe()) return "You have edited this drawing."
|
||||
}
|
||||
|
||||
function in_iframe () {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
@ -1,190 +0,0 @@
|
||||
var clipboard = (function () {
|
||||
|
||||
var exports = {
|
||||
format: "mirc",
|
||||
importing: false,
|
||||
visible: false,
|
||||
canvas: document.createElement("canvas"),
|
||||
canvas_r: document.createElement("canvas"),
|
||||
|
||||
bind: function () {
|
||||
import_button.addEventListener("click", exports.import_colorcode)
|
||||
import_textarea.addEventListener("focus", exports.focus)
|
||||
import_textarea.addEventListener("blur", exports.blur)
|
||||
import_textarea.addEventListener('paste', exports.paste)
|
||||
},
|
||||
setFormat: function (name) {
|
||||
return function () {
|
||||
clipboard.format = name
|
||||
if (! clipboard.importing) { clipboard.export_data() }
|
||||
}
|
||||
},
|
||||
show: function () { import_wrapper.style.display = "block"; clipboard.visible = true; changed = false },
|
||||
hide: function () { import_wrapper.style.display = "none"; clipboard.visible = false },
|
||||
focus: function () {
|
||||
if (! clipboard.importing) {
|
||||
import_textarea.focus()
|
||||
import_textarea.select()
|
||||
}
|
||||
},
|
||||
blur: function () {
|
||||
},
|
||||
|
||||
import_mode: function () {
|
||||
focus()
|
||||
clipboard.importing = true
|
||||
format_el.style.display = 'none'
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
import_buttons.style.display = "inline"
|
||||
import_textarea.value = ""
|
||||
},
|
||||
|
||||
export_mode: function () {
|
||||
focus()
|
||||
clipboard.importing = false
|
||||
import_buttons.style.display = "none"
|
||||
format_el.style.display = 'inline'
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
clipboard.export_data()
|
||||
},
|
||||
|
||||
paste: function (e) {
|
||||
e.preventDefault()
|
||||
// images will come through as files
|
||||
var types = toArray(e.clipboardData.types)
|
||||
import_textarea.value = ""
|
||||
types.forEach(function(type, i){
|
||||
console.log(type)
|
||||
// this can be text/plain or text/html..
|
||||
if (type.match('text/plain')) {
|
||||
import_textarea.value = e.clipboardData.getData(type)
|
||||
}
|
||||
else {
|
||||
console.error("unknown type!", item.type)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
import_colorcode: function (data, no_undo) {
|
||||
if (data && data.preventDefault) {
|
||||
data = import_textarea.value
|
||||
} else {
|
||||
data = data || import_textarea.value
|
||||
}
|
||||
|
||||
var to_json = function(string, opts){
|
||||
var lines_in = string.split(/\r?\n/)
|
||||
var lines_out = []
|
||||
var w = 0, h = 0
|
||||
for (var y = 0; y < lines_in.length; y++) {
|
||||
var bg = 1, fg = 15
|
||||
var cells = [], line = lines_in[y]
|
||||
if (line.length === 0) {
|
||||
continue
|
||||
} else {
|
||||
for (var x = 0; x < line.length; x++) {
|
||||
switch (line[x]) {
|
||||
case "\x02": // ^B (unimplemented)
|
||||
break
|
||||
case "\x03": // ^C
|
||||
var parseColour = function(line, x) {
|
||||
if (/1[0-5]/.test(line.substr(x, 2))) {
|
||||
colour = parseInt(line.substr(x, 2))
|
||||
return [colour, x + 2]
|
||||
} else if (/0[0-9]/.test(line.substr(x, 2))) {
|
||||
colour = parseInt(line.substr(x, 2))
|
||||
return [colour, x + 2]
|
||||
} else if (/[0-9]/.test(line.substr(x, 1))) {
|
||||
colour = parseInt(line.substr(x, 1))
|
||||
return [colour, x + 1]
|
||||
} else {
|
||||
return [undefined, x]
|
||||
}
|
||||
}
|
||||
var bg_ = undefined, fg_ = undefined, x_ = x + 1;
|
||||
[fg_, x_] = parseColour(line, x_)
|
||||
if (line[x_] === ",") {
|
||||
[bg_, x_] = parseColour(line, x_ + 1)
|
||||
}
|
||||
if ((bg_ == undefined) && (fg_ == undefined)) {
|
||||
[bg, fg] = [1, 15]
|
||||
} else {
|
||||
bg = (bg_ != undefined) ? bg_ : bg;
|
||||
fg = (fg_ != undefined) ? fg_ : fg;
|
||||
};
|
||||
if (x_ != x) {x = x_ - 1}; break;
|
||||
case "\x06": // ^F (unimplemented)
|
||||
break
|
||||
case "\x0f": // ^O
|
||||
[bg, fg] = [1, 15]; break;
|
||||
case "\x16": // ^V
|
||||
[bg, fg] = [fg, bg]; break;
|
||||
case "\x1f": // ^_ (unimplemented)
|
||||
break
|
||||
default:
|
||||
cells.push({bg: bg, fg: fg, value: line[x]})
|
||||
}
|
||||
}
|
||||
if (cells.length > 0) {
|
||||
if (w < cells.length) {
|
||||
w = cells.length
|
||||
}
|
||||
lines_out.push(cells); h++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {h: h, lines: lines_out, w: w}
|
||||
}
|
||||
var json = to_json(data, {fg:0, bg:1})
|
||||
|
||||
if (!no_undo) undo.new()
|
||||
if (!no_undo) undo.save_rect(0,0, canvas.w, canvas.h)
|
||||
if (json.w !== canvas.w || json.h !== canvas.h){
|
||||
if (!no_undo) undo.save_size(canvas.w, canvas.h)
|
||||
canvas.resize(json.w, json.h, true)
|
||||
}
|
||||
canvas.clear()
|
||||
|
||||
for (var y = 0, line; line = json.lines[y]; y++){
|
||||
var row = canvas.aa[y]
|
||||
for (var x = 0, char; char = line[x]; x++){
|
||||
var lex = row[x]
|
||||
lex.char = char.value
|
||||
lex.fg = char.fg
|
||||
lex.bg = char.bg
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
}
|
||||
}
|
||||
|
||||
current_filetool && current_filetool.blur()
|
||||
},
|
||||
|
||||
export_data: function () {
|
||||
var output
|
||||
// switch (clipboard.format) {
|
||||
switch (controls.save_format.value) {
|
||||
case 'ascii':
|
||||
output = canvas.ascii()
|
||||
break
|
||||
case 'mirc':
|
||||
output = canvas.mirc({cutoff: 425})
|
||||
break
|
||||
}
|
||||
if (output.cutoff){
|
||||
cutoff_warning_el.style.display = 'block'
|
||||
} else {
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
}
|
||||
import_textarea.value = output
|
||||
clipboard.focus()
|
||||
return output
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
return exports
|
||||
|
||||
})()
|
||||
|
||||
|
@ -1,106 +0,0 @@
|
||||
|
||||
var fillColor = 1 // black
|
||||
|
||||
var color_names = ("white black dark-blue green red dark-red purple orange " +
|
||||
"yellow lime teal cyan blue magenta dark-gray light-gray").split(" ");
|
||||
|
||||
var all_color_hue_order = "dark-red red orange yellow lime green teal cyan blue dark-blue purple magenta black dark-gray light-gray white".split(" ");
|
||||
var all_color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green white light-gray dark-gray black".split(" ");
|
||||
var color_hue_order = "dark-red red orange yellow lime cyan teal blue dark-blue purple magenta".split(" ");
|
||||
var color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green".split(" ");
|
||||
var gray_names = ("black dark-gray light-gray white").split(" ")
|
||||
|
||||
var fire_names = ("black dark-red red orange yellow white cyan").split(" ")
|
||||
var red_names = ("black dark-red red").split(" ")
|
||||
var yellow_names = ("black orange yellow white").split(" ")
|
||||
var green_names = ("teal green lime").split(" ")
|
||||
var blue_names = ("black dark-blue blue").split(" ")
|
||||
var purple_names = ("dark-blue purple magenta red").split(" ")
|
||||
var dark_gray_names = ("black dark-blue teal dark-gray light-gray white").split(" ")
|
||||
var color_alphabet = "abcdefghijklmnop";
|
||||
var colors = {}
|
||||
color_names.forEach(function(name, i){
|
||||
colors[name.replace("-", "")] = i
|
||||
colors[name] = i
|
||||
})
|
||||
colors.brown = 5
|
||||
|
||||
function get_inverse (n) { return colors[all_color_inv_order.indexOf(color_names[n])] }
|
||||
|
||||
function mirc_color (n) { return mod(n, 16)|0 }
|
||||
function mirc_color_reverse (n) { return mod(-(n+1), 16)|0 }
|
||||
function all_hue (n) { return colors[all_color_hue_order[mod(n, 16)|0]] }
|
||||
function all_inv_hue (n) { return colors[all_color_inv_order[mod(n, 16)|0]] }
|
||||
function hue (n) { return colors[color_hue_order[mod(n, 11)|0]] }
|
||||
function rand_hue () { return colors[color_hue_order[randint(11)]] }
|
||||
function rand_gray () { return colors[gray_names[randint(4)]] }
|
||||
function inv_hue (n) { return colors[color_inv_order[mod(n, 11)|0]] }
|
||||
function gray (n) { return colors[gray_names[mod(n, 4)|0]] }
|
||||
function fire (n) { return colors[fire_names[mod(n, 7)|0]] }
|
||||
function red (n) { return colors[red_names[mod(n, 3)|0]] }
|
||||
function yellow (n) { return colors[yellow_names[mod(n, 4)|0]] }
|
||||
function green (n) { return colors[green_names[mod(n, 3)|0]] }
|
||||
function blue (n) { return colors[blue_names[mod(n, 3)|0]] }
|
||||
function purple (n) { return colors[purple_names[mod(n, 4)|0]] }
|
||||
function dark_gray (n) { return colors[dark_gray_names[mod(n, 4)|0]] }
|
||||
|
||||
var css_lookup = {
|
||||
'rgb(255, 255, 255)': 'A',
|
||||
'rgb(0, 0, 0)': 'B',
|
||||
'rgb(0, 0, 127)': 'C',
|
||||
'rgb(0, 147, 0)': 'D',
|
||||
'red': 'E',
|
||||
'rgb(127, 0, 0)': 'F',
|
||||
'rgb(156, 0, 156)': 'G',
|
||||
'rgb(252, 127, 0)': 'H',
|
||||
'rgb(255, 255, 0)': 'I',
|
||||
'rgb(0, 252, 0)': 'J',
|
||||
'rgb(0, 147, 147)': 'K',
|
||||
'rgb(0, 255, 255)': 'L',
|
||||
'rgb(0, 0, 252)': 'M',
|
||||
'rgb(255, 0, 255)': 'N',
|
||||
'rgb(127, 127, 127)': 'O',
|
||||
'rgb(210, 210, 210)': 'P',
|
||||
};
|
||||
var css_reverse_lookup = {}
|
||||
Object.keys(css_lookup).forEach(function(color){
|
||||
css_reverse_lookup[ css_lookup[color].charCodeAt(0) - 65 ] = color
|
||||
})
|
||||
|
||||
var ansi_fg = [
|
||||
97, // white
|
||||
30, // black
|
||||
34, // dark blue
|
||||
32, // green
|
||||
91, // light red
|
||||
31, // dark red
|
||||
35, // purple
|
||||
33, // "dark yellow" (orange?)
|
||||
93, // "light yellow"
|
||||
92, // light green
|
||||
36, // cyan (teal?)
|
||||
96, // light cyan
|
||||
94, // light blue
|
||||
95, // light magenta
|
||||
90, // dark gray
|
||||
37, // light gray
|
||||
]
|
||||
|
||||
var ansi_bg = [
|
||||
107, // white
|
||||
40, // black
|
||||
44, // dark blue
|
||||
42, // green
|
||||
101, // light red
|
||||
41, // dark red
|
||||
45, // purple
|
||||
43, // yellow (orange)
|
||||
103, // light yellow
|
||||
102, // light green
|
||||
46, // cyan (teal?)
|
||||
106, // light cyan
|
||||
104, // light blue
|
||||
105, // light magenta
|
||||
100, // dark gray
|
||||
47, // light gray
|
||||
]
|
@ -1,294 +0,0 @@
|
||||
|
||||
var blit = (function(){
|
||||
var blit = {}
|
||||
blit.and = blit.atop = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && lex.opacity > 0) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.or = blit.under = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && cell.opacity == 0) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
// copy the region of A beginning at x,y into B
|
||||
blit.copy_from = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell) {
|
||||
lex.assign(cell)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.copy_toroidal_from = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.get(u+x, v+y)
|
||||
if (cell) {
|
||||
lex.assign(cell)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.copy_to = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.invert = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && lex.opacity > 0) {
|
||||
cell.fg = get_inverse(cell.fg)
|
||||
cell.bg = get_inverse(cell.bg)
|
||||
}
|
||||
})
|
||||
}
|
||||
var distance_rect = function(x, y, ratio){
|
||||
return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2))
|
||||
}
|
||||
var distance_square = function(x, y, ratio){
|
||||
return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x * ratio, 2))
|
||||
}
|
||||
blit.circle = function(A, lex){
|
||||
var hw = brush.w/2, hh = brush.h/2
|
||||
var ratio, distance
|
||||
|
||||
if (brush.w === brush.h){
|
||||
distance = distance_square
|
||||
ratio = hw / hh * (brush.w === 3 || brush.w === 5 ? 1.2 : 1.05)
|
||||
} else {
|
||||
distance = distance_rect
|
||||
ratio = hw / hh
|
||||
}
|
||||
|
||||
A.forEach(function(lex,x,y) {
|
||||
if (distance(x - hw + 0.5, y - hh + 0.5, ratio) > hw){
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.cross = function(A, lex){
|
||||
A.forEach(function(lex,x,y) {
|
||||
if ((x+y)%2) {
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.inverted_cross = function(A, lex){
|
||||
// 1x1 brush should still draw something
|
||||
if (A.w == 1 && A.h == 1) {
|
||||
return
|
||||
}
|
||||
A.forEach(function(lex,x,y) {
|
||||
if (!((x+y)%2)) {
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.square = function(A, lex){
|
||||
// i.e. no transparency
|
||||
}
|
||||
return blit
|
||||
})()
|
||||
|
||||
var draw = (function(){
|
||||
|
||||
var last_point = [0,0]
|
||||
|
||||
function down (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
changed = true
|
||||
if (e.shiftKey) {
|
||||
line (lex, last_point, point, erasing)
|
||||
}
|
||||
else {
|
||||
stamp (canvas, brush, point[0], point[1], erasing)
|
||||
}
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function set_last_point (e, point) {
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function move (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
line(lex, last_point, point, erasing)
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function move_toroidal (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
var src_x_quantile = quantile( last_point[0], w )
|
||||
var src_y_quantile = quantile( last_point[1], h )
|
||||
var dst_x_quantile = quantile( point[0], w )
|
||||
var dst_y_quantile = quantile( point[1], h )
|
||||
var src_x_mod = mod( last_point[0], w )
|
||||
var src_y_mod = mod( last_point[1], h )
|
||||
var dst_x_mod = mod( point[0], w )
|
||||
var dst_y_mod = mod( point[1], h )
|
||||
// if we've moved across the edge of the board, draw two lines
|
||||
if (src_x_quantile != dst_x_quantile || src_y_quantile != dst_y_quantile) {
|
||||
var xa, ya
|
||||
if (src_x_quantile < dst_x_quantile) {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod + w],
|
||||
[src_x_mod-w, dst_x_mod],
|
||||
]
|
||||
}
|
||||
else if (src_x_quantile == dst_x_quantile) {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod],
|
||||
[src_x_mod, dst_x_mod],
|
||||
]
|
||||
}
|
||||
else {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod-w],
|
||||
[src_x_mod+w, dst_x_mod],
|
||||
]
|
||||
}
|
||||
|
||||
if (src_y_quantile < dst_y_quantile) {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod + h],
|
||||
[src_y_mod-h, dst_y_mod],
|
||||
]
|
||||
}
|
||||
else if (src_y_quantile == dst_y_quantile) {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod],
|
||||
[src_y_mod, dst_y_mod],
|
||||
]
|
||||
}
|
||||
else {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod-h],
|
||||
[src_y_mod+h, dst_y_mod],
|
||||
]
|
||||
}
|
||||
line(lex, [ xa[0][0], ya[0][0] ], [ xa[0][1], ya[0][1] ], erasing)
|
||||
line(lex, [ xa[1][0], ya[1][0] ], [ xa[1][1], ya[1][1] ], erasing)
|
||||
}
|
||||
else {
|
||||
var x_a = mod( last_point[0], w )
|
||||
var y_a = mod( last_point[1], h )
|
||||
var x_b = mod( point[0], w )
|
||||
var y_b = mod( point[1], h )
|
||||
var last_point_mod = [x_b, y_b], point_mod = [x_a, y_a]
|
||||
line(lex, last_point_mod, point_mod, erasing)
|
||||
}
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
// y = point.y
|
||||
}
|
||||
|
||||
function point (lex, x, y, erasing) {
|
||||
stamp (canvas, brush, x, y, erasing)
|
||||
}
|
||||
|
||||
function line (lex, a, b, erasing) {
|
||||
var len = dist(a[0], a[1], b[0], b[1])
|
||||
var bw = 1
|
||||
var x, y, i;
|
||||
for (var i = 0; i <= len; i += bw) {
|
||||
x = lerp(i / len, a[0], b[0])
|
||||
y = lerp(i / len, a[1], b[1])
|
||||
stamp (canvas, brush, x, y, erasing)
|
||||
}
|
||||
}
|
||||
|
||||
function stamp (canvas, brush, x, y, erasing) {
|
||||
var hh = brush.w/2|0
|
||||
brush.forEach(function(lex, s, t){
|
||||
s = round( s + x-hh )
|
||||
t = round( t + y-hh )
|
||||
if (s >= 0 && s < canvas.w && t >= 0 && t < canvas.h) {
|
||||
if (lex.opacity === 0 && lex.char === ' ') return;
|
||||
var aa = canvas.aa[t][s]
|
||||
undo.save_lex(s, t, aa)
|
||||
if (erasing) {
|
||||
aa.erase(lex)
|
||||
}
|
||||
else {
|
||||
aa.stamp(lex, brush)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function fill (lex, x, y) {
|
||||
var q = [ [x,y] ]
|
||||
var aa = canvas.aa
|
||||
var target = aa[y][x].clone()
|
||||
var n, w = 0, e = 0, j = 0
|
||||
var kk = 0
|
||||
// gets into a weird infinite loop if we don't break here.. :\
|
||||
if (target.eq(lex)) { return }
|
||||
LOOP: while (q.length) {
|
||||
n = q.shift()
|
||||
if (aa[n[1]][n[0]].ne(target)) {
|
||||
continue LOOP
|
||||
}
|
||||
w = e = n[0]
|
||||
j = n[1]
|
||||
WEST: while (w > 0) {
|
||||
if (aa[j][w-1].eq(target)) {
|
||||
w = w-1
|
||||
}
|
||||
else {
|
||||
break WEST
|
||||
}
|
||||
}
|
||||
EAST: while (e < canvas.w-1) {
|
||||
if (aa[j][e+1].eq(target)) {
|
||||
e = e+1
|
||||
}
|
||||
else {
|
||||
break EAST
|
||||
}
|
||||
}
|
||||
for (var i = w; i <= e; i++) {
|
||||
undo.save_lex(i, j, aa[j][i])
|
||||
aa[j][i].assign(lex)
|
||||
if (j > 0 && aa[j-1][i].eq(target)) {
|
||||
q.push([ i, j-1 ])
|
||||
}
|
||||
if (j < canvas.h-1 && aa[j+1][i].eq(target)) {
|
||||
q.push([ i, j+1 ])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var draw = {}
|
||||
draw.down = down
|
||||
draw.set_last_point = set_last_point
|
||||
draw.move = move
|
||||
draw.move_toroidal = move_toroidal
|
||||
draw.stamp = stamp
|
||||
draw.line = line
|
||||
draw.point = point
|
||||
draw.fill = fill
|
||||
return draw
|
||||
|
||||
})()
|
||||
|
@ -1,134 +0,0 @@
|
||||
function Lex (x,y) {
|
||||
if (typeof x == "number") {
|
||||
this.y = y
|
||||
this.x = x
|
||||
this.span = document.createElement("span")
|
||||
}
|
||||
else {
|
||||
this.span = x
|
||||
}
|
||||
this.fg = colors.white
|
||||
this.bg = colors.black
|
||||
this.char = " "
|
||||
this.opacity = 1
|
||||
this.focused = false
|
||||
}
|
||||
Lex.prototype.build = function(){
|
||||
if (isNaN(this.bg) || this.bg == Infinity || this.bg == -Infinity) this.bg = colors.black
|
||||
if (isNaN(this.fg) || this.fg == Infinity || this.fg == -Infinity) this.fg = colors.black
|
||||
this.span.className = this.css()
|
||||
this.span.innerHTML = this.html()
|
||||
}
|
||||
Lex.prototype.css = function(){
|
||||
return (
|
||||
this.focused ?
|
||||
"focused " : ""
|
||||
) + (
|
||||
this.opacity === 0 ?
|
||||
"transparent f" + color_alphabet[modi(this.fg,16)] :
|
||||
"f" + color_alphabet[modi(this.fg,16)] + " b" + color_alphabet[modi(this.bg,16)]
|
||||
)
|
||||
}
|
||||
Lex.prototype.html = function(){
|
||||
return this.char == " " ? " " : this.char || " "
|
||||
}
|
||||
Lex.prototype.read = function(){
|
||||
this.char = this.span.innerHTML
|
||||
return this.char
|
||||
}
|
||||
Lex.prototype.ascii = function(){
|
||||
return this.char || " "
|
||||
}
|
||||
Lex.prototype.sanitize = function(){
|
||||
switch (this.char) {
|
||||
// case "%": return "%"
|
||||
case undefined:
|
||||
case "": return " "
|
||||
default: return this.char
|
||||
}
|
||||
}
|
||||
Lex.prototype.mirc = function(bg_, fg_){
|
||||
var char = this.char || " "
|
||||
var charIsNaN = isNaN(parseInt(char))
|
||||
if ((bg_ == this.fg) && (fg_ == this.bg)) {
|
||||
bg_ = this.bg; fg_ = this.fg
|
||||
return [bg_, fg_, "\x16" + char]
|
||||
} else if ((bg_ != this.bg) && (fg_ == this.fg)) {
|
||||
bg_ = this.bg
|
||||
return [bg_, fg_, "\x03," + ((this.bg&15) < 10 && !charIsNaN ? "0" : "") + (this.bg&15) + char]
|
||||
} else if ((bg_ == this.bg) && (fg_ != this.fg)) {
|
||||
fg_ = this.fg
|
||||
return [bg_, fg_, "\x03" + ((this.fg&15) < 10 && !charIsNaN ? "0" : "") + (this.fg&15) + char]
|
||||
} else {
|
||||
bg_ = this.bg; fg_ = this.fg
|
||||
return [bg_, fg_, "\x03" + (this.fg&15) + "," + ((this.bg&15) < 10 && !charIsNaN ? "0" : "") + (this.bg&15) + char]
|
||||
}
|
||||
}
|
||||
Lex.prototype.assign = function (lex){
|
||||
this.fg = lex.fg
|
||||
this.bg = lex.bg
|
||||
this.char = lex.char
|
||||
this.opacity = lex.opacity
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.stamp = function (lex, brush){
|
||||
if (brush.draw_fg) this.fg = lex.fg
|
||||
if (brush.draw_bg && lex.opacity > 0) this.bg = lex.bg
|
||||
if (brush.draw_char) this.char = lex.char
|
||||
this.opacity = 1
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.clone = function () {
|
||||
var lex = new Lex (0,0)
|
||||
lex.assign(this)
|
||||
return lex
|
||||
}
|
||||
Lex.prototype.erase = function (){
|
||||
this.fg = fillColor
|
||||
this.bg = fillColor
|
||||
this.char = " "
|
||||
this.opacity = 1
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.eq = function(lex){
|
||||
return lex && this.fg == lex.fg && this.bg == lex.bg && this.char == lex.char
|
||||
}
|
||||
Lex.prototype.eqColor = function(lex){
|
||||
return lex && this.fg == lex.fg && this.bg == lex.bg
|
||||
}
|
||||
Lex.prototype.ne = function(lex){
|
||||
return ! this.eq(lex)
|
||||
}
|
||||
Lex.prototype.clear = function(){
|
||||
this.bg = colors.black
|
||||
this.fg = 0
|
||||
this.char = " "
|
||||
this.opacity = 0
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.isClear = function(){
|
||||
return this.bg == 1 && this.fg == 0 && this.char == " "
|
||||
}
|
||||
Lex.prototype.focus = function(){
|
||||
if (focused) focused.blur()
|
||||
this.span.classList.add('focused')
|
||||
this.focused = true
|
||||
focused = this
|
||||
}
|
||||
Lex.prototype.blur = function(){
|
||||
focused = null
|
||||
this.span && this.span.classList.remove('focused')
|
||||
this.focused = false
|
||||
this.onBlur && this.onBlur()
|
||||
}
|
||||
Lex.prototype.demolish = function(){
|
||||
if (this.span.parentNode) { this.span.parentNode.removeChild(this.span) }
|
||||
this.span = null
|
||||
}
|
||||
Lex.prototype.key = function(char, keyCode) {
|
||||
if (! char) { return }
|
||||
this.char = char
|
||||
this.fg = brush.fg
|
||||
this.build()
|
||||
return true
|
||||
}
|
@ -1,497 +0,0 @@
|
||||
function Matrix (w,h,f){
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.f = f
|
||||
this.focus_x = 0
|
||||
this.focus_y = 0
|
||||
this.initialize()
|
||||
}
|
||||
Matrix.prototype.initialize = function(f){
|
||||
var w = this.w || 1, h = this.h || 1, f = f || this.f
|
||||
var aa = new Array (h)
|
||||
for (var y = 0; y < h; y++) {
|
||||
aa[y] = new Array (w)
|
||||
for (var x = 0; x < w; x++) {
|
||||
aa[y][x] = f(x,y)
|
||||
}
|
||||
}
|
||||
this.aa = aa
|
||||
}
|
||||
Matrix.prototype.rebuild = function (){
|
||||
this.demolish()
|
||||
this.initialize()
|
||||
this.append()
|
||||
this.bind()
|
||||
this.generate && this.generate()
|
||||
this.focus_clamp()
|
||||
check_if_lost_focus()
|
||||
}
|
||||
Matrix.prototype.clone = function () {
|
||||
var base = this
|
||||
var clone = new Matrix(this.w, this.h, function(x,y){
|
||||
return base.getCell(x,y).clone()
|
||||
})
|
||||
clone.f = this.f
|
||||
return clone
|
||||
}
|
||||
Matrix.prototype.assign = function (mat) {
|
||||
var base = this
|
||||
this.demolish()
|
||||
this.w = mat.w
|
||||
this.h = mat.h
|
||||
// this.f = function(){}
|
||||
this.initialize(function(x,y){
|
||||
var el = mat.getCell(x,y).clone()
|
||||
el.build()
|
||||
return el
|
||||
})
|
||||
this.append()
|
||||
this.bind()
|
||||
check_if_lost_focus()
|
||||
return this
|
||||
}
|
||||
|
||||
Matrix.prototype.bind = function () {}
|
||||
Matrix.prototype.demolish = function (){
|
||||
this.forEach(function(lex){
|
||||
lex.demolish()
|
||||
})
|
||||
while (this.wrapper && this.wrapper.firstChild) {
|
||||
this.wrapper.removeChild(this.wrapper.firstChild);
|
||||
}
|
||||
this.aa.forEach(function(row){
|
||||
row.length = 0
|
||||
})
|
||||
this.aa.length = 0
|
||||
}
|
||||
Matrix.prototype.forEach = function(f){
|
||||
this.aa.forEach(function(row, y){
|
||||
row.forEach(function(lex, x){
|
||||
f(lex, x, y)
|
||||
})
|
||||
})
|
||||
}
|
||||
Matrix.prototype.focus_clamp = function(){
|
||||
this.focus_x = clamp(this.focus_x, 0, this.w - 1)
|
||||
this.focus_y = clamp(this.focus_y, 0, this.h - 1)
|
||||
}
|
||||
Matrix.prototype.focus_add = function(x, y){
|
||||
this.focus(this.focus_x + x, this.focus_y + y)
|
||||
}
|
||||
Matrix.prototype.focus = function(x, y){
|
||||
if (x === undefined) x = this.focus_x
|
||||
if (y === undefined) y = this.focus_y
|
||||
x = mod(x, this.w)
|
||||
y = mod(y, this.h)
|
||||
this.focus_x = x
|
||||
this.focus_y = y
|
||||
|
||||
//focused_input = this
|
||||
this.aa[y][x].focus()
|
||||
}
|
||||
Matrix.prototype.focusLex = function(y,x){
|
||||
if (x < 0) {
|
||||
y -= 1
|
||||
}
|
||||
if (x > this.aa[0].length) {
|
||||
y += 1
|
||||
}
|
||||
this.aa[mod(y,this.h)][mod(x,this.w)].focus()
|
||||
}
|
||||
Matrix.prototype.clear = function(){
|
||||
this.forEach(function(lex,x,y){ lex.clear() })
|
||||
}
|
||||
Matrix.prototype.erase = function(){
|
||||
this.forEach(function(lex,x,y){ lex.erase() })
|
||||
}
|
||||
Matrix.prototype.fill = function(lex){
|
||||
this.fg = lex.fg
|
||||
this.bg = lex.bg
|
||||
this.char = lex.char
|
||||
this.opacity = lex.opacity
|
||||
this.forEach(function(el,x,y){
|
||||
el.assign(lex)
|
||||
el.build()
|
||||
})
|
||||
}
|
||||
|
||||
Matrix.prototype.build = function(){
|
||||
this.forEach(function(lex,x,y){
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
Matrix.prototype.append = function(wrapper){
|
||||
wrapper = this.wrapper = wrapper || this.wrapper
|
||||
if (! this.wrapper) return
|
||||
this.aa.forEach(function(row, y){
|
||||
var div = document.createElement("div")
|
||||
row.forEach(function(lex, x) {
|
||||
div.appendChild(lex.span)
|
||||
})
|
||||
wrapper.appendChild( div )
|
||||
})
|
||||
}
|
||||
Matrix.prototype.region = function(w,h,x,y) {
|
||||
w = w || 1
|
||||
h = h || 1
|
||||
x = x || 0
|
||||
y = y || 0
|
||||
var parent = this
|
||||
var mat = new Matrix(w, h, function(x,y){
|
||||
return parent.aa[y][x]
|
||||
})
|
||||
mat.f = this.f
|
||||
return mat
|
||||
}
|
||||
Matrix.prototype.setCell = function(lex,x,y){
|
||||
this.aa[y] && this.aa[y][x] && this.aa[y][x].assign(lex)
|
||||
}
|
||||
Matrix.prototype.getCell = function(x,y){
|
||||
if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
|
||||
else return null
|
||||
}
|
||||
Matrix.prototype.get = function(x,y){
|
||||
y = floor(mod(y || 0, this.h))
|
||||
x = floor(mod(x || 0, this.w))
|
||||
if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
|
||||
else return null
|
||||
}
|
||||
|
||||
Matrix.prototype.resize = function(w,h){
|
||||
w = w || canvas.w
|
||||
h = h || canvas.h
|
||||
var div, row, lex
|
||||
var f = this.f, old_h = this.aa.length, old_w = this.aa[0].length
|
||||
var wrapper = this.wrapper
|
||||
w = max(w, 1)
|
||||
h = max(h, 1)
|
||||
if (h < old_h) {
|
||||
for (var y = old_h; y > h; y--) {
|
||||
row = this.aa.pop()
|
||||
div = row[0].span.parentNode
|
||||
row.forEach(function(lex, x){
|
||||
lex.demolish()
|
||||
})
|
||||
div.parentNode.removeChild(div)
|
||||
}
|
||||
}
|
||||
else if (h > old_h) {
|
||||
for (var y = old_h; y < h; y++) {
|
||||
div = document.createElement("div")
|
||||
wrapper.appendChild( div )
|
||||
this.aa[y] = new Array (w)
|
||||
for (var x = 0; x < w; x++) {
|
||||
lex = this.aa[y][x] = f(x,y)
|
||||
div.appendChild(lex.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w < old_w) {
|
||||
this.aa.forEach(function(row, y){
|
||||
while (row.length > w) {
|
||||
lex = row.pop()
|
||||
lex.demolish()
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (w > old_w) {
|
||||
this.aa.forEach(function(row, y){
|
||||
div = row[0].span.parentNode
|
||||
for (var x = row.length; x < w; x++) {
|
||||
lex = row[x] = f(x,y)
|
||||
div.appendChild(lex.span)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.bind && this.bind()
|
||||
this.focus_clamp()
|
||||
if (this.wrapper && this.wrapper.parentNode != document.body) {
|
||||
this.resize_wrapper()
|
||||
}
|
||||
}
|
||||
Matrix.prototype.resize_wrapper = function(){
|
||||
var cell = canvas.aa[0][0].span
|
||||
var cw = cell.offsetWidth
|
||||
var ch = cell.offsetHeight
|
||||
// if (canvas.grid) { ch++ }
|
||||
var width = cw * this.aa[0].length
|
||||
var height = ch * this.aa.length
|
||||
if (canvas.grid) { width++; height++ }
|
||||
this.wrapper.parentNode.style.height = ""
|
||||
this.wrapper.style.width =
|
||||
this.wrapper.parentNode.style.width = (width) + "px"
|
||||
this.wrapper.style.top = ""
|
||||
}
|
||||
Matrix.prototype.ascii = function () {
|
||||
var lines = this.aa.map(function(row, y){
|
||||
var last, line = ""
|
||||
row.forEach(function(lex, x) {
|
||||
line += lex.ascii()
|
||||
})
|
||||
return line // .replace(/\s+$/,"")
|
||||
})
|
||||
var txt = lines.join("\n")
|
||||
return txt
|
||||
}
|
||||
Matrix.prototype.mirc = function (opts) {
|
||||
var cutoff = false
|
||||
var lines = this.aa.map(function(row, y){
|
||||
var last, line = ""
|
||||
row.forEach(function(lex, x) {
|
||||
var bg_ = -1, fg_ = 15
|
||||
if (lex.eqColor(last)) {
|
||||
line += lex.sanitize()
|
||||
}
|
||||
else {
|
||||
[bg_, fg_, line_] = lex.mirc(bg_, fg_)
|
||||
line += line_; last = lex;
|
||||
}
|
||||
})
|
||||
if (opts && opts.cutoff && line.length > opts.cutoff) {
|
||||
cutoff = true
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
var txt = lines.filter(function(line){ return line.length > 0 }).join('\n')
|
||||
|
||||
if (cutoff) {
|
||||
txt = new String(txt)
|
||||
txt.cutoff = true
|
||||
}
|
||||
return txt
|
||||
}
|
||||
|
||||
var undo = (function(){
|
||||
|
||||
var max_states = 200;
|
||||
|
||||
// undotimetotal = 0;
|
||||
|
||||
var stack = {undo: [], redo: []};
|
||||
var current_undo = null;
|
||||
var dom = {undo: undo_el, redo: redo_el};
|
||||
dom.undo.is_visible = dom.redo.is_visible = false
|
||||
|
||||
var LexState = function(lex){
|
||||
this.fg = lex.fg;
|
||||
this.bg = lex.bg;
|
||||
this.char = lex.char;
|
||||
this.opacity = lex.opacity;
|
||||
};
|
||||
|
||||
var update_dom_visibility = function(type){
|
||||
var el = dom[type]
|
||||
if (el.is_visible){
|
||||
if (stack[type].length === 0) {
|
||||
el.classList.add('hidden')
|
||||
el.is_visible = false
|
||||
}
|
||||
} else if (stack[type].length > 0){
|
||||
el.classList.remove('hidden')
|
||||
el.is_visible = true
|
||||
}
|
||||
}
|
||||
var update_dom = function(){
|
||||
update_dom_visibility('undo')
|
||||
update_dom_visibility('redo')
|
||||
}
|
||||
|
||||
// state is an undo or redo state that might contain these props
|
||||
// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill)
|
||||
// focus: {x:, y: },
|
||||
// size: {w:, h: },
|
||||
// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...]
|
||||
// }
|
||||
var new_state = function(){
|
||||
var state = {lexs:{}};
|
||||
save_focus(canvas.focus_x, canvas.focus_y, state)
|
||||
return state
|
||||
}
|
||||
var new_redo = function(){
|
||||
return new_state()
|
||||
}
|
||||
var new_undo = function(){
|
||||
current_undo = new_state()
|
||||
stack.redo = []
|
||||
stack.undo.push(current_undo)
|
||||
if (stack.undo.length > max_states) stack.undo.shift();
|
||||
update_dom()
|
||||
return current_undo
|
||||
}
|
||||
|
||||
var save_focus = function(x, y, state){
|
||||
state = state || current_undo
|
||||
state.focus = {x:x, y:y}
|
||||
}
|
||||
var save_size = function(w, h, state){
|
||||
state = state || current_undo
|
||||
state.size = {w:w, h:h};
|
||||
}
|
||||
// the reason for stringifying the x y coords is so that each
|
||||
// coordinate is saved only once in an undo state.
|
||||
// otherwise there would be problems with, eg, a brush stroke
|
||||
// that passed over the same grid cell twice.
|
||||
var save_lex = function(x, y, lex, state){
|
||||
// var start = Date.now()
|
||||
state = state || current_undo
|
||||
var lexs = state.lexs;
|
||||
var xy = x + "," + y;
|
||||
if (xy in lexs) return;
|
||||
lexs[xy] = new LexState(lex)
|
||||
// undotimetotal += Date.now() - start
|
||||
}
|
||||
var save_focused_lex = function(state){
|
||||
state = state || current_undo
|
||||
var x = canvas.focus_x
|
||||
var y = canvas.focus_y
|
||||
save_lex(x, y, canvas.aa[y][x], state)
|
||||
}
|
||||
var save_rect = function(xpos, ypos, w, h, state){
|
||||
if (w === 0 || h === 0) return;
|
||||
state = state || current_undo;
|
||||
state.rects = state.rects || []
|
||||
var aa = canvas.aa;
|
||||
var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []}
|
||||
var lexs = rect.lexs
|
||||
var xlen = xpos + w
|
||||
var ylen = ypos + h
|
||||
for (var y = ypos; y < ylen; y++){
|
||||
var aay = aa[y]
|
||||
for (var x = xpos; x < xlen; x++){
|
||||
lexs.push(new LexState(aay[x]))
|
||||
}
|
||||
}
|
||||
state.rects.push(rect)
|
||||
}
|
||||
var save_resize = function(w, h, old_w, old_h, state){
|
||||
state = state || current_undo
|
||||
save_size(old_w, old_h, state)
|
||||
if (old_w > w){
|
||||
// .---XX
|
||||
// | XX
|
||||
// |___XX
|
||||
save_rect(w, 0, old_w - w, old_h, state)
|
||||
if (old_h > h){
|
||||
// .----.
|
||||
// | |
|
||||
// XXXX_|
|
||||
save_rect(0, h, w, old_h - h, state)
|
||||
}
|
||||
} else if (old_h > h){
|
||||
// .----.
|
||||
// | |
|
||||
// XXXXXX
|
||||
save_rect(0, h, old_w, old_h - h, state)
|
||||
}
|
||||
}
|
||||
|
||||
var restore_state = function(state){
|
||||
// all redo states will have a cached undo state on them
|
||||
// an undo state might have a cached redo state
|
||||
// if it doesn't have one, generate one
|
||||
var make_redo = ! ('redo' in state || 'undo' in state);
|
||||
var aa = canvas.aa
|
||||
var lex, lexs;
|
||||
|
||||
if (make_redo){
|
||||
state.redo = new_redo()
|
||||
|
||||
// copy saved rects that intersect with current canvas size
|
||||
// important to do this before resizing canvas
|
||||
if ('rects' in state){
|
||||
for (var ri=0, rect; rect=state.rects[ri]; ri++){
|
||||
if (rect.x >= canvas.w ||
|
||||
rect.y >= canvas.h) continue;
|
||||
var w = Math.min(rect.w, canvas.w - rect.x)
|
||||
var h = Math.min(rect.h, canvas.h - rect.y)
|
||||
save_rect(rect.x, rect.y, w, h, state.redo)
|
||||
}
|
||||
}
|
||||
if ('size' in state){
|
||||
save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo)
|
||||
}
|
||||
}
|
||||
|
||||
if ('size' in state){
|
||||
canvas.resize(state.size.w, state.size.h, true);
|
||||
}
|
||||
|
||||
if ('rects' in state){
|
||||
for (var ri=0, rect; rect=state.rects[ri]; ri++){
|
||||
lexs = rect.lexs
|
||||
for (var li=0; lex=lexs[li]; li++){
|
||||
var x = (li % rect.w) + rect.x
|
||||
var y = ((li / rect.w)|0) + rect.y
|
||||
aa[y][x].assign(lex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lexs = state.lexs
|
||||
for (var key in lexs){
|
||||
var xy = key.split(',');
|
||||
lex = aa[xy[1]][xy[0]]
|
||||
if (make_redo)
|
||||
save_lex(xy[0], xy[1], lex, state.redo)
|
||||
lex.assign(lexs[key])
|
||||
}
|
||||
|
||||
if ('focus' in state){
|
||||
canvas.focus_x = state.focus.x
|
||||
canvas.focus_y = state.focus.y
|
||||
if (current_canvas === canvas){
|
||||
canvas.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var undo = function(){
|
||||
var state = stack.undo.pop();
|
||||
if (!state) return;
|
||||
|
||||
restore_state(state)
|
||||
|
||||
// now take the applied undo state and store it on the redo state
|
||||
// and push the redo state to the redo stack
|
||||
state.redo.undo = state
|
||||
stack.redo.push(state.redo)
|
||||
delete state.redo
|
||||
|
||||
update_dom()
|
||||
}
|
||||
|
||||
var redo = function(){
|
||||
var state = stack.redo.pop();
|
||||
if (!state) return;
|
||||
|
||||
restore_state(state)
|
||||
|
||||
state.undo.redo = state
|
||||
stack.undo.push(state.undo)
|
||||
delete state.undo
|
||||
|
||||
update_dom()
|
||||
}
|
||||
|
||||
return {
|
||||
stack: stack,
|
||||
new: new_undo,
|
||||
// new_redo: new_redo,
|
||||
save_focus: save_focus,
|
||||
save_size: save_size,
|
||||
save_lex: save_lex,
|
||||
save_focused_lex: save_focused_lex,
|
||||
save_rect: save_rect,
|
||||
save_resize: save_resize,
|
||||
undo: undo,
|
||||
redo: redo
|
||||
}
|
||||
|
||||
})()
|
@ -1,138 +0,0 @@
|
||||
var brush = (function(){
|
||||
|
||||
var brush = new Matrix (5, 5, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
brush.modified = false
|
||||
|
||||
brush.mask = blit.circle
|
||||
|
||||
brush.generate = function(){
|
||||
brush.fill(brush)
|
||||
brush.mask(brush)
|
||||
}
|
||||
|
||||
brush.bind = function(){
|
||||
|
||||
var last_point = [0,0]
|
||||
var dragging = false
|
||||
var erasing = false
|
||||
|
||||
brush.forEach(function(lex, x, y){
|
||||
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
var point = [x,y]
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
current_canvas = brush
|
||||
brush.modified = true
|
||||
dragging = true
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
if (erasing) {
|
||||
lex.clear()
|
||||
}
|
||||
else {
|
||||
fillColor = brush.bg
|
||||
lex.assign(brush)
|
||||
}
|
||||
brush.focus(x, y)
|
||||
})
|
||||
lex.span.addEventListener('mousemove', function(e){
|
||||
e.preventDefault()
|
||||
if (! dragging) {
|
||||
return
|
||||
}
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
if (erasing) {
|
||||
lex.clear()
|
||||
}
|
||||
else {
|
||||
lex.assign(brush)
|
||||
}
|
||||
brush.focus(x, y)
|
||||
})
|
||||
})
|
||||
window.addEventListener('mouseup', function(e){
|
||||
dragging = erasing = false
|
||||
})
|
||||
}
|
||||
|
||||
brush.resize = function(w, h){
|
||||
w = this.w = clamp(w, this.min, this.max)
|
||||
h = this.h = clamp(h, this.min, this.max)
|
||||
brush.rebuild()
|
||||
controls.brush_w.char = "" + w
|
||||
controls.brush_w.build()
|
||||
controls.brush_h.char = "" + h
|
||||
controls.brush_h.build()
|
||||
}
|
||||
brush.size_add = function(w, h){
|
||||
brush.resize(brush.w + w, brush.h + h)
|
||||
}
|
||||
brush.expand = function(i){
|
||||
brush.size_add(i, i)
|
||||
}
|
||||
brush.contract = function(i){
|
||||
brush.size_add(-i, -i)
|
||||
}
|
||||
|
||||
brush.load = function(lex){
|
||||
brush.char = lex.char
|
||||
brush.fg = lex.fg
|
||||
brush.bg = lex.bg
|
||||
brush.opacity = 1
|
||||
}
|
||||
|
||||
brush.min = 1
|
||||
brush.max = 100
|
||||
|
||||
brush.char = " "
|
||||
brush.fg = 0
|
||||
brush.bg = 1
|
||||
brush.opacity = 1
|
||||
|
||||
brush.draw_fg = true
|
||||
brush.draw_bg = true
|
||||
brush.draw_char = true
|
||||
|
||||
return brush
|
||||
|
||||
})()
|
||||
|
||||
var custom = (function(){
|
||||
|
||||
var exports = {}
|
||||
|
||||
exports.clone = function (){
|
||||
var new_brush = brush.clone()
|
||||
var wrapper = document.createElement("div")
|
||||
wrapper.className = "custom"
|
||||
new_brush.append(wrapper)
|
||||
custom_wrapper.appendChild(wrapper)
|
||||
// store in localstorage?
|
||||
wrapper.addEventListener("click", function(e){
|
||||
if (e.shiftKey) {
|
||||
wrapper.parentNode.removeChild(wrapper)
|
||||
delete new_brush
|
||||
} else {
|
||||
// load this brush
|
||||
exports.load(new_brush)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.load = function(new_brush){
|
||||
brush.assign( new_brush )
|
||||
}
|
||||
|
||||
return exports
|
||||
|
||||
})()
|
@ -1,102 +0,0 @@
|
||||
var canvas = current_canvas = (function(){
|
||||
|
||||
var cols = 100
|
||||
var rows = 30
|
||||
|
||||
var canvas = new Matrix (cols, rows, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
canvas.bind = function(){
|
||||
|
||||
canvas.forEach(function(lex, x, y){
|
||||
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
var point = [x,y]
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
dragging = true
|
||||
current_canvas = canvas
|
||||
if (e.altKey) {
|
||||
if (e.shiftKey) {
|
||||
blit.copy_from(canvas, brush, floor(x-brush.w/2), floor(y-brush.h/2))
|
||||
brush.mask(brush)
|
||||
draw.set_last_point(e, point)
|
||||
}
|
||||
else {
|
||||
brush.load(lex)
|
||||
brush.generate()
|
||||
dragging = false
|
||||
}
|
||||
return
|
||||
}
|
||||
else if (drawing) {
|
||||
undo.new()
|
||||
draw.down(e, lex, point)
|
||||
}
|
||||
else if (selecting) {
|
||||
selection.down(e, lex, point)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.down(e, lex, point)
|
||||
}
|
||||
else if (filling) {
|
||||
undo.new()
|
||||
draw.fill(brush, x, y)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
|
||||
lex.span.addEventListener("mousemove", function(e){
|
||||
mouse.x = x
|
||||
mouse.y = y
|
||||
if (! dragging) return
|
||||
if (drawing) {
|
||||
draw.move(e, lex, point)
|
||||
}
|
||||
else if (selecting) {
|
||||
selection.move(e, lex, point)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.move(e, lex, point)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
canvas.min = 1
|
||||
canvas.max = 999
|
||||
|
||||
// canvas.resize(1, 1, true) // wont create undo state
|
||||
canvas.resize = function(w, h, no_undo){
|
||||
var old_w = this.w, old_h = this.h
|
||||
w = this.w = clamp(w, this.min, this.max)
|
||||
h = this.h = clamp(h, this.min, this.max)
|
||||
if (old_w === w && old_h === h) return;
|
||||
|
||||
if (!no_undo){
|
||||
undo.new()
|
||||
undo.save_resize(w, h, old_w, old_h)
|
||||
}
|
||||
|
||||
canvas.__proto__.resize.call(canvas, w, h)
|
||||
controls.canvas_w.char = "" + w
|
||||
controls.canvas_w.build()
|
||||
controls.canvas_h.char = "" + h
|
||||
controls.canvas_h.build()
|
||||
}
|
||||
canvas.size_add = function(w, h){
|
||||
canvas.resize(canvas.w + w, canvas.h + h)
|
||||
}
|
||||
|
||||
return canvas
|
||||
|
||||
})()
|
@ -1,253 +0,0 @@
|
||||
var controls = (function(){
|
||||
|
||||
var controls = {}
|
||||
|
||||
controls.cross = new Tool (cross_el)
|
||||
controls.cross.use = function(){
|
||||
if (brush.mask == blit.cross) {
|
||||
controls.cross.el.innerHTML = "ssoɹɔ"
|
||||
brush.mask = blit.inverted_cross
|
||||
}
|
||||
else {
|
||||
controls.cross.el.innerHTML = "cross"
|
||||
brush.mask = blit.cross
|
||||
}
|
||||
brush.generate()
|
||||
drawing = true
|
||||
brush.modified = false
|
||||
}
|
||||
controls.cross.done = function(){
|
||||
controls.cross.el.innerHTML = "cross"
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.circle = new Tool (circle_el)
|
||||
controls.circle.use = function(){
|
||||
brush.mask = blit.circle
|
||||
brush.generate()
|
||||
drawing = true
|
||||
brush.modified = false
|
||||
}
|
||||
controls.circle.done = function(){
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.square = new Tool (square_el)
|
||||
controls.square.use = function(){
|
||||
brush.mask = blit.square
|
||||
brush.generate()
|
||||
brush.modified = false
|
||||
drawing = true
|
||||
}
|
||||
controls.square.done = function(){
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.text = new Tool (text_el)
|
||||
controls.text.use = function(){
|
||||
current_filetool && current_filetool.blur()
|
||||
}
|
||||
|
||||
controls.select = new Tool (select_el)
|
||||
controls.select.use = function(){
|
||||
selection.show()
|
||||
}
|
||||
controls.select.done = function(){
|
||||
selection.hide()
|
||||
}
|
||||
|
||||
controls.rotate = new Tool (rotate_el)
|
||||
controls.rotate.use = function(){
|
||||
transform.set_mode('rotate')
|
||||
}
|
||||
controls.rotate.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.scale = new Tool (scale_el)
|
||||
controls.scale.use = function(){
|
||||
transform.set_mode('scale')
|
||||
}
|
||||
controls.scale.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.slice = new Tool (slice_el)
|
||||
controls.slice.use = function(){
|
||||
transform.set_mode('slice')
|
||||
}
|
||||
controls.slice.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.translate = new Tool (translate_el)
|
||||
controls.translate.use = function(){
|
||||
transform.set_mode('translate')
|
||||
}
|
||||
controls.translate.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.fill = new Tool (fill_el)
|
||||
controls.fill.use = function(){
|
||||
filling = true
|
||||
document.body.classList.add("bucket")
|
||||
}
|
||||
controls.fill.done = function(){
|
||||
filling = false
|
||||
document.body.classList.remove("bucket")
|
||||
}
|
||||
|
||||
controls.undo = new BlurredTool (undo_el)
|
||||
controls.undo.use = function(){
|
||||
undo.undo()
|
||||
}
|
||||
|
||||
controls.redo = new BlurredTool (redo_el)
|
||||
controls.redo.use = function(){
|
||||
undo.redo()
|
||||
}
|
||||
|
||||
controls.clear = new BlurredTool (clear_el)
|
||||
controls.clear.use = function(){
|
||||
undo.new()
|
||||
undo.save_rect(0, 0, canvas.w, canvas.h)
|
||||
canvas.erase()
|
||||
current_filetool && current_filetool.blur()
|
||||
}
|
||||
|
||||
controls.grid = new BlurredCheckbox (grid_el)
|
||||
controls.grid.memorable = true
|
||||
controls.grid.use = function(state){
|
||||
state = typeof state == "boolean" ? state : ! document.body.classList.contains("grid")
|
||||
document.body.classList[ state ? "add" : "remove" ]('grid')
|
||||
letters.grid = palette.grid = canvas.grid = state
|
||||
canvas.resize_wrapper()
|
||||
palette.resize_wrapper()
|
||||
letters.resize_wrapper()
|
||||
if (! selection.hidden) selection.reposition()
|
||||
this.update( state )
|
||||
}
|
||||
ClipboardTool = FileTool.extend({
|
||||
blur: function(){
|
||||
this.__blur()
|
||||
clipboard.hide()
|
||||
}
|
||||
})
|
||||
controls.save = new ClipboardTool (save_el)
|
||||
controls.save.use = function(){
|
||||
clipboard.show()
|
||||
clipboard.export_mode()
|
||||
}
|
||||
controls.load = new ClipboardTool (load_el)
|
||||
controls.load.use = function(){
|
||||
// console.log("use")
|
||||
clipboard.show()
|
||||
clipboard.import_mode()
|
||||
}
|
||||
|
||||
controls.save_format = new RadioGroup(format_el)
|
||||
controls.save_format.name = 'save_format'
|
||||
controls.save_format.memorable = true
|
||||
var cs = controls.save_format.controls
|
||||
cs.mirc.use = cs.ascii.use = function(){
|
||||
clipboard.export_data()
|
||||
}
|
||||
//
|
||||
|
||||
controls.experimental_palette = new HiddenCheckbox (experimental_palette_toggle)
|
||||
controls.experimental_palette.memorable = true
|
||||
controls.experimental_palette.use = function(state){
|
||||
var state = palette.experimental(state)
|
||||
this.update(state)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
controls.fg = new BlurredCheckbox (fg_checkbox)
|
||||
controls.fg.use = function(state){
|
||||
brush.draw_fg = state || ! brush.draw_fg
|
||||
this.update(brush.draw_fg)
|
||||
}
|
||||
|
||||
controls.bg = new BlurredCheckbox (bg_checkbox)
|
||||
controls.bg.use = function(state){
|
||||
brush.draw_bg = state || ! brush.draw_bg
|
||||
this.update(brush.draw_bg)
|
||||
}
|
||||
|
||||
controls.char = new BlurredCheckbox (char_checkbox)
|
||||
controls.char.use = function(state){
|
||||
brush.draw_char = state || ! brush.draw_char
|
||||
this.update(brush.draw_char)
|
||||
}
|
||||
|
||||
controls.vertical = new BlurredCheckbox (vertical_checkbox)
|
||||
controls.vertical.memorable = true
|
||||
controls.vertical.use = function(state){
|
||||
canvas.vertical = typeof state == "boolean" ? state : ! canvas.vertical
|
||||
controls.vertical.refresh()
|
||||
}
|
||||
controls.vertical.refresh = function(){
|
||||
if (canvas.vertical) {
|
||||
document.body.classList.add("vertical")
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove("vertical")
|
||||
}
|
||||
palette.repaint()
|
||||
letters.repaint()
|
||||
this.update(canvas.vertical)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
controls.brush_w = new Lex (brush_w_el)
|
||||
controls.brush_h = new Lex (brush_h_el)
|
||||
controls.canvas_w = new Lex (canvas_w_el)
|
||||
controls.canvas_h = new Lex (canvas_h_el)
|
||||
|
||||
// bind
|
||||
|
||||
controls.bind = function(){
|
||||
|
||||
for (var n in controls){
|
||||
var control = controls[n]
|
||||
if (typeof control === 'object' && 'bind' in control){
|
||||
control.bind()
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
controls.brush_w,
|
||||
controls.brush_h,
|
||||
controls.canvas_w,
|
||||
controls.canvas_h
|
||||
].forEach(function(lex){
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
lex.focus()
|
||||
})
|
||||
});
|
||||
|
||||
controls.brush_w.key = keys.single_numeral_key(controls.brush_w, function(w){ brush.resize(w, brush.h) })
|
||||
controls.brush_w.raw_key = keys.arrow_key(function(w){ brush.size_add(w, 0) })
|
||||
|
||||
controls.brush_h.key = keys.single_numeral_key(controls.brush_h, function(h){ brush.resize(brush.w, h) })
|
||||
controls.brush_h.raw_key = keys.arrow_key(function(h){ brush.size_add(0, h) })
|
||||
|
||||
controls.canvas_w.key = keys.multi_numeral_key(controls.canvas_w, 3)
|
||||
controls.canvas_w.onBlur = keys.multi_numeral_blur(controls.canvas_w, function(w){ canvas.resize(w, canvas.h) })
|
||||
controls.canvas_w.raw_key = keys.arrow_key(function(w){ canvas.size_add(w, 0) })
|
||||
|
||||
controls.canvas_h.key = keys.multi_numeral_key(controls.canvas_h, 3)
|
||||
controls.canvas_h.onBlur = keys.multi_numeral_blur(controls.canvas_h, function(h){ canvas.resize(canvas.w, h) })
|
||||
controls.canvas_h.raw_key = keys.arrow_key(function(h){ canvas.size_add(0, h) })
|
||||
|
||||
add_custom_el.addEventListener("click", function(){
|
||||
custom.clone()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return controls
|
||||
})()
|
@ -1,209 +0,0 @@
|
||||
var keys = (function(){
|
||||
|
||||
var keys = {}
|
||||
keys.bind = function(){
|
||||
cursor_input.addEventListener('keydown', function(e){
|
||||
|
||||
// console.log("keycode:", e.keyCode)
|
||||
if (e.altKey) {
|
||||
document.body.classList.add("dropper")
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 27: // esc
|
||||
if (!selection.hidden && current_canvas === canvas){
|
||||
selection.hide()
|
||||
selection.show()
|
||||
} else if (focused){
|
||||
focused.blur()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (window.focused && focused.raw_key) {
|
||||
focused.raw_key(e)
|
||||
return
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 219: // [
|
||||
if (current_tool.name != "text") {
|
||||
e.preventDefault()
|
||||
brush.contract(1)
|
||||
brush.modified = false
|
||||
check_if_lost_focus()
|
||||
}
|
||||
break
|
||||
case 221: // ]
|
||||
if (current_tool.name != "text") {
|
||||
e.preventDefault()
|
||||
brush.expand(1)
|
||||
brush.modified = false
|
||||
}
|
||||
break
|
||||
case 8: // backspace
|
||||
e.preventDefault()
|
||||
if (current_canvas === canvas)
|
||||
undo.new()
|
||||
current_canvas.focus_add(-1, 0)
|
||||
if (current_canvas === canvas)
|
||||
undo.save_focused_lex()
|
||||
focused.char = " "
|
||||
focused.build()
|
||||
return
|
||||
case 13: // return
|
||||
e.preventDefault()
|
||||
current_canvas.focusLex(focused.y, focused.x+1)
|
||||
return
|
||||
case 38: // up
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(0, -1)
|
||||
break
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(0, 1)
|
||||
break
|
||||
case 37: // left
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(-1, 0)
|
||||
break
|
||||
case 39: // right
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(1, 0)
|
||||
break
|
||||
// use typical windows and os x shortcuts
|
||||
// undo: ctrl-z or cmd-z
|
||||
// redo: ctrl-y or shift-cmd-z
|
||||
case 89: // y
|
||||
if (!e.ctrlKey && !e.metaKey) break;
|
||||
e.preventDefault();
|
||||
undo.redo();
|
||||
break
|
||||
case 90: // z
|
||||
if (!e.ctrlKey && !e.metaKey) break;
|
||||
e.preventDefault();
|
||||
if (e.shiftKey)
|
||||
undo.redo();
|
||||
else
|
||||
undo.undo();
|
||||
break
|
||||
// default:
|
||||
// if (focused) { focused.key(undefined, e.keyCode) }
|
||||
}
|
||||
})
|
||||
|
||||
cursor_input.addEventListener('input', function(e){
|
||||
/*
|
||||
if (! e.metaKey && ! e.ctrlKey && ! e.altKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
*/
|
||||
var char = cursor_input.value
|
||||
cursor_input.value = ""
|
||||
|
||||
// console.log("input:", char)
|
||||
|
||||
if (current_tool.name != "text" && ! brush.modified) {
|
||||
brush.char = char
|
||||
if (char == " ") {
|
||||
brush.bg = brush.fg
|
||||
}
|
||||
else if (brush.bg != fillColor) {
|
||||
brush.fg = brush.bg
|
||||
brush.bg = fillColor
|
||||
}
|
||||
brush.rebuild()
|
||||
}
|
||||
|
||||
if (focused && char) {
|
||||
var y = focused.y, x = focused.x
|
||||
if (current_canvas === canvas){
|
||||
undo.new()
|
||||
undo.save_focused_lex()
|
||||
}
|
||||
var moving = focused.key(char, e.keyCode)
|
||||
if ( ! moving || ! ('y' in focused && 'x' in focused) ) { return }
|
||||
current_canvas.focus_add(1, 0)
|
||||
}
|
||||
})
|
||||
|
||||
cursor_input.addEventListener("keyup", function(e){
|
||||
if (! e.altKey) {
|
||||
document.body.classList.remove("dropper")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
keys.int_key = function (f) {
|
||||
return function (key, keyCode) {
|
||||
var n = parseInt(key)
|
||||
! isNaN(n) && f(n)
|
||||
}
|
||||
}
|
||||
|
||||
keys.arrow_key = function (fn) {
|
||||
return function (e){
|
||||
switch (e.keyCode) {
|
||||
case 38: // up
|
||||
e.preventDefault()
|
||||
fn(1)
|
||||
break
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
fn(-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
keys.left_right_key = function (fn) {
|
||||
return function (e){
|
||||
switch (e.keyCode) {
|
||||
case 39: // right
|
||||
e.preventDefault()
|
||||
fn(1)
|
||||
break
|
||||
case 38: // up
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
fn(0)
|
||||
break
|
||||
case 37: // left
|
||||
e.preventDefault()
|
||||
fn(-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys.single_numeral_key = function (lex, fn) {
|
||||
return keys.int_key(function(n, keyCode){
|
||||
if (n == 0) n = 10
|
||||
lex.blur()
|
||||
fn(n)
|
||||
})
|
||||
}
|
||||
keys.multi_numeral_key = function (lex, digits){
|
||||
return keys.int_key(function(n, keyCode){
|
||||
lex.read()
|
||||
if (lex.char.length < digits) {
|
||||
n = parseInt(lex.char) * 10 + n
|
||||
}
|
||||
lex.char = ""+n
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
keys.multi_numeral_blur = function (lex, fn){
|
||||
return function(){
|
||||
var n = parseInt(lex.char)
|
||||
if (isNaN(n)) return
|
||||
fn(n)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
})()
|
||||
|
||||
function check_if_lost_focus() {
|
||||
if (! window.focused || ! window.focused.span)
|
||||
window.focused = canvas.aa[0][0]
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
var letters = (function(){
|
||||
|
||||
var last_charset = ""
|
||||
var charset_index = 0
|
||||
var charsets = [
|
||||
'Basic Latin',
|
||||
'Latin-1 Supplement',
|
||||
'Box Drawing',
|
||||
'Block Elements',
|
||||
]
|
||||
|
||||
var letters = new Matrix (1, 1, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
return lex
|
||||
})
|
||||
|
||||
letters.charset = ""
|
||||
|
||||
letters.repaint = function(charset){
|
||||
letters.charset = charset = charset || last_charset
|
||||
last_charset = charset
|
||||
var chars = unicode.block(charset, 32)
|
||||
if (chars[0] != " ") chars.unshift(" ")
|
||||
if (canvas.vertical) {
|
||||
letters.resize( Math.ceil( chars.length / 16 ), 16 )
|
||||
}
|
||||
else {
|
||||
letters.resize( 32, Math.ceil( chars.length / 32 ) )
|
||||
}
|
||||
|
||||
var i = 0
|
||||
|
||||
letters.forEach(function(lex,x,y){
|
||||
if (canvas.vertical) { x=x^y;y=x^y;x=x^y }
|
||||
var char = chars[i++]
|
||||
if (palette.chars.indexOf(brush.char) > 1) {
|
||||
lex.bg = brush.fg
|
||||
lex.fg = brush.bg
|
||||
}
|
||||
else {
|
||||
lex.bg = colors.black
|
||||
lex.fg = brush.fg == fillColor ? colors.black : brush.fg
|
||||
}
|
||||
lex.char = char
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
|
||||
letters.bind = function(){
|
||||
letters.forEach(function(lex,x,y){
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
charset_index = (charset_index+1) % charsets.length
|
||||
letters.repaint(charsets[charset_index])
|
||||
return
|
||||
}
|
||||
else if (e.ctrlKey || e.which == 3) {
|
||||
brush.char = lex.char
|
||||
brush.bg = brush.fg
|
||||
brush.fg = fillColor
|
||||
}
|
||||
else {
|
||||
brush.char = lex.char
|
||||
if (lex.char == " ") {
|
||||
brush.bg = brush.fg
|
||||
}
|
||||
else if (brush.bg != fillColor) {
|
||||
brush.fg = brush.bg
|
||||
brush.bg = fillColor
|
||||
}
|
||||
}
|
||||
if (! brush.modified) {
|
||||
brush.generate()
|
||||
}
|
||||
palette.repaint()
|
||||
})
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return letters
|
||||
})()
|
||||
|
||||
var palette = (function(){
|
||||
|
||||
var palette = new Matrix (32, 2, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
return lex
|
||||
})
|
||||
|
||||
var palette_index = localStorage.getItem("ascii.palette") || 1
|
||||
var palette_list = [all_hue, all_inv_hue, mirc_color, mirc_color_reverse]
|
||||
var palette_fn = palette_list[palette_index]
|
||||
var dither = {
|
||||
aa: '▓▒░ ',
|
||||
a: '▓',
|
||||
b: '▒',
|
||||
c: '░',
|
||||
d: ' ',
|
||||
p: function(n){
|
||||
return dither.aa[Math.floor(Math.abs(n) % 4)]
|
||||
}
|
||||
}
|
||||
palette.chars = " " + dither.a + dither.b + dither.c
|
||||
|
||||
palette.repaint = function(){
|
||||
var xw = use_experimental_palette ? 5 : 2
|
||||
if (canvas.vertical) {
|
||||
palette.resize( xw, 16 )
|
||||
}
|
||||
else {
|
||||
palette.resize( 32, xw )
|
||||
}
|
||||
|
||||
palette.forEach(function(lex,x,y){
|
||||
if (canvas.vertical) { x=x^y;y=x^y;x=x^y;x*=2 }
|
||||
if (y < 2) {
|
||||
lex.bg = palette_fn(x>>1)
|
||||
lex.fg = palette_fn(x>>1)
|
||||
}
|
||||
else {
|
||||
lex.bg = fillColor
|
||||
lex.fg = palette_fn(x>>1)
|
||||
}
|
||||
lex.char = palette.chars[y]
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
if (lex.char == "_") lex.char = " "
|
||||
})
|
||||
}
|
||||
palette.repaint()
|
||||
var use_experimental_palette = false
|
||||
palette.experimental = function(state){
|
||||
use_experimental_palette = typeof state == "boolean" ? state : ! use_experimental_palette
|
||||
use_experimental_palette ? palette.resize(32, 5) : palette.resize(32, 2)
|
||||
palette.repaint()
|
||||
return use_experimental_palette
|
||||
}
|
||||
|
||||
palette.bind = function(){
|
||||
palette.forEach(function(lex, x, y){
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
palette_index = (palette_index+1) % palette_list.length
|
||||
localStorage.setItem("ascii.palette", palette_index)
|
||||
palette_fn = palette_list[palette_index]
|
||||
palette.repaint()
|
||||
return
|
||||
}
|
||||
if (e.ctrlKey || e.which == 3) return
|
||||
if (brush.char == " " && lex.char == " ") {
|
||||
brush.fg = lex.fg
|
||||
brush.bg = lex.bg
|
||||
brush.char = lex.char
|
||||
}
|
||||
else if (lex.char != " ") {
|
||||
brush.fg = lex.bg
|
||||
brush.bg = lex.fg
|
||||
brush.char = lex.char
|
||||
}
|
||||
else {
|
||||
brush.fg = lex.bg
|
||||
brush.bg = fillColor
|
||||
// brush.char = lex.char
|
||||
}
|
||||
brush.opacity = lex.opacity
|
||||
if (! brush.modified) {
|
||||
brush.generate()
|
||||
}
|
||||
if (filling || e.ctrlKey) {
|
||||
fillColor = lex.bg
|
||||
}
|
||||
letters.repaint()
|
||||
})
|
||||
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
fillColor = y ? lex.fg : lex.bg
|
||||
palette.repaint()
|
||||
brush.fg = lex.fg
|
||||
brush.char = lex.char
|
||||
brush.opacity = lex.opacity
|
||||
brush.generate()
|
||||
brush_wrapper.style.borderColor = css_reverse_lookup[fillColor]
|
||||
return
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
brush_wrapper.style.borderColor = css_reverse_lookup[fillColor]
|
||||
|
||||
return palette
|
||||
|
||||
})()
|
@ -1,159 +0,0 @@
|
||||
var selection = (function(){
|
||||
|
||||
var creating = false, moving = false, copying = false
|
||||
|
||||
var selection_canvas = new Matrix (1, 1, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
var selector_el = document.createElement("div")
|
||||
selector_el.className = "selector_el"
|
||||
selection_canvas.append(selector_el)
|
||||
document.body.appendChild(selector_el)
|
||||
|
||||
// in selection mode..
|
||||
// - we start by clicking the canvas. this positions the selection, and copies
|
||||
// the character
|
||||
// - then we drag down and to the right. this resizes the selection and pushes new
|
||||
// rows and columns. each of these copies the character underneath.
|
||||
// - on mouseup, the selection is locked. then..
|
||||
// - drag the selection to move it -- this "cuts" it and leaves a blank space on the canvas.
|
||||
// - shift-drag the selection to copy it
|
||||
|
||||
var a = [0, 0]
|
||||
var b = [0, 0]
|
||||
var c = [0, 0]
|
||||
var d = [0, 0]
|
||||
|
||||
function reset () {
|
||||
a[0] = a[1] = b[0] = b[1] = 0
|
||||
}
|
||||
function left (a,b) { return min(a[0],b[0]) }
|
||||
function top (a,b) { return min(a[1],b[1]) }
|
||||
function right (a,b) { return max(a[0],b[0]) }
|
||||
function bottom (a,b) { return max(a[1],b[1]) }
|
||||
function width (a,b) { return abs(a[0]-b[0])+1 }
|
||||
function height (a,b) { return abs(a[1]-b[1])+1 }
|
||||
function mag_x (a,b) { return a[0]-b[0] }
|
||||
function mag_y (a,b) { return a[1]-b[1] }
|
||||
function orient (a,b) {
|
||||
var l = left(a,b), m = top(a,b), n = right(a,b), o = bottom(a,b)
|
||||
a[0] = l ; a[1] = m ; b[0] = n ; b[1] = o
|
||||
}
|
||||
|
||||
function contains (a,b,point) {
|
||||
var contains_x = a[0] <= point[0] && point[0] <= b[0]
|
||||
var contains_y = a[1] <= point[1] && point[1] <= b[1]
|
||||
return (contains_x && contains_y)
|
||||
}
|
||||
function reposition (aa, bb) {
|
||||
aa = aa || a
|
||||
bb = bb || b
|
||||
var cell = canvas.aa[top(aa, bb)][left(aa, bb)].span
|
||||
var cell_left = cell.offsetLeft
|
||||
var cell_top = cell.offsetTop
|
||||
var cell_width = cell.offsetWidth
|
||||
var cell_height = cell.offsetHeight
|
||||
|
||||
var w = width(aa, bb)
|
||||
var h = height(aa, bb)
|
||||
|
||||
selector_el.style.top = (cell_top-1) + "px"
|
||||
selector_el.style.left = (cell_left-1) + "px"
|
||||
selector_el.style.width = (cell_width*w+1) + "px"
|
||||
selector_el.style.height = (cell_height*h+1) + "px"
|
||||
}
|
||||
function down (e, lex, point){
|
||||
if ( ! contains(a,b,point) ) {
|
||||
copying = false
|
||||
moving = false
|
||||
creating = true
|
||||
a[0] = point[0]
|
||||
a[1] = point[1]
|
||||
b[0] = point[0]
|
||||
b[1] = point[1]
|
||||
reposition(a,b)
|
||||
selection.hidden = false
|
||||
selector_el.classList.add("creating")
|
||||
} else {
|
||||
copying = false
|
||||
moving = true
|
||||
creating = false
|
||||
c[0] = point[0]
|
||||
c[1] = point[1]
|
||||
d[0] = point[0]
|
||||
d[1] = point[1]
|
||||
}
|
||||
show()
|
||||
selector_el.classList.remove("dragging")
|
||||
}
|
||||
function move (e, lex, point){
|
||||
if (creating) {
|
||||
b[0] = point[0]
|
||||
b[1] = point[1]
|
||||
reposition(a,b)
|
||||
}
|
||||
else if (moving) {
|
||||
d[0] = point[0]
|
||||
d[1] = point[1]
|
||||
var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
|
||||
var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
|
||||
reposition( [ a[0] + dx, a[1] + dy ], [ b[0] + dx, b[1] + dy ])
|
||||
}
|
||||
else if (copying) {
|
||||
}
|
||||
}
|
||||
function up (e) {
|
||||
if (creating) {
|
||||
orient(a,b)
|
||||
selection_canvas.resize(width(a,b), height(a,b))
|
||||
reposition(a,b)
|
||||
blit.copy_from( canvas, selection_canvas, a[0], a[1] )
|
||||
selection_canvas.build()
|
||||
selector_el.classList.remove("creating")
|
||||
}
|
||||
if (moving) {
|
||||
var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
|
||||
var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
|
||||
a[0] += dx
|
||||
a[1] += dy
|
||||
b[0] += dx
|
||||
b[1] += dy
|
||||
undo.new()
|
||||
undo.save_rect(a[0], a[1], b[0] - a[0] + 1, b[1] - a[1] + 1)
|
||||
blit.copy_to( canvas, selection_canvas, a[0], a[1] )
|
||||
}
|
||||
if (copying) {
|
||||
}
|
||||
creating = moving = copying = false
|
||||
selector_el.classList.remove("dragging")
|
||||
}
|
||||
|
||||
function show () {
|
||||
selecting = true
|
||||
}
|
||||
function hide () {
|
||||
reset()
|
||||
selector_el.style.top = "-9999px"
|
||||
selector_el.style.left = "-9999px"
|
||||
selector_el.style.width = "0px"
|
||||
selector_el.style.height = "0px"
|
||||
creating = moving = copying = false
|
||||
selection.hidden = true
|
||||
selecting = false
|
||||
}
|
||||
|
||||
var selection = {}
|
||||
selection.reposition = reposition
|
||||
selection.down = down
|
||||
selection.move = move
|
||||
selection.up = up
|
||||
selection.canvas = selection_canvas
|
||||
selection.show = show
|
||||
selection.hide = hide
|
||||
selection.hidden = true
|
||||
return selection
|
||||
|
||||
})()
|
@ -1,170 +0,0 @@
|
||||
var Tool = Model({
|
||||
init: function (el) {
|
||||
this.el = el
|
||||
this.lex = new Lex (el)
|
||||
this.name = el.innerHTML
|
||||
},
|
||||
bind: function(){
|
||||
var tool = this
|
||||
tool.el.addEventListener('mousedown', function(e){
|
||||
tool.focus()
|
||||
})
|
||||
tool.el.addEventListener('contextmenu', function(e){
|
||||
tool.context(e)
|
||||
})
|
||||
if (tool.memorable) {
|
||||
// console.log(tool.name, localStorage.getItem("ascii.tools." + tool.name) )
|
||||
tool.use( localStorage.getItem("ascii.tools." + tool.name) == "true" )
|
||||
}
|
||||
},
|
||||
use: function(){},
|
||||
context: function(e){},
|
||||
done: function(){},
|
||||
focus: function(){
|
||||
// focused && focused.blur()
|
||||
current_tool && current_tool.blur()
|
||||
current_tool = this
|
||||
this.el.classList.add('focused')
|
||||
this.use()
|
||||
cursor_input.focus()
|
||||
},
|
||||
blur: function(){
|
||||
current_tool = null
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var FileTool = Tool.extend({
|
||||
focus: function(){
|
||||
if (current_filetool === this) {
|
||||
this.blur()
|
||||
return
|
||||
}
|
||||
current_filetool && current_filetool.blur()
|
||||
current_filetool = this
|
||||
this.el.classList.add('focused')
|
||||
this.use()
|
||||
cursor_input.focus()
|
||||
},
|
||||
blur: function(){
|
||||
current_filetool = null
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var RadioItem = Tool.extend({
|
||||
init: function(group, el){
|
||||
this.group = group
|
||||
this.el = el
|
||||
},
|
||||
focus: function(){
|
||||
this.el.classList.add('focused')
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
},
|
||||
bind: function(){
|
||||
var control = this
|
||||
this.el.addEventListener('mousedown', function(){
|
||||
control.group.use(control)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var RadioGroup = Tool.extend({
|
||||
init: function(el){
|
||||
this.el = el
|
||||
this.controls = {}
|
||||
var names = el.innerHTML.split(' ')
|
||||
el.innerHTML = ''
|
||||
var group = this
|
||||
names.forEach(function(value){
|
||||
var el = document.createElement('span')
|
||||
el.classList.add('radio','tool')
|
||||
var control = new RadioItem(group, el)
|
||||
if (value.substr(0,1) === '*') {
|
||||
control.value = value = value.substr(1)
|
||||
group.use(control)
|
||||
}
|
||||
control.value = el.innerHTML = value
|
||||
group.controls[value] = control
|
||||
group.el.appendChild(el)
|
||||
})
|
||||
},
|
||||
use: function(control){
|
||||
if (typeof control === 'string') {
|
||||
control = this.controls[control]
|
||||
}
|
||||
this.selected_control && this.selected_control.blur()
|
||||
this.value = control.value
|
||||
this.selected_control = control
|
||||
control.focus()
|
||||
control.use()
|
||||
if (this.memorable){
|
||||
localStorage.setItem("ascii.tools." + this.name, this.value)
|
||||
}
|
||||
},
|
||||
bind: function(){
|
||||
var tool = this
|
||||
for (var n in this.controls){
|
||||
this.controls[n].bind()
|
||||
}
|
||||
if (tool.memorable) {
|
||||
var value = localStorage.getItem("ascii.tools." + tool.name)
|
||||
if (value) tool.use(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var Checkbox = Tool.extend({
|
||||
init: function (el){
|
||||
this.__init(el)
|
||||
var name = this.name.replace(/^[x_] /,"")
|
||||
var state = localStorage.getItem("ascii.tools." + name) == "true" || this.name[0] == "x"
|
||||
this.name = name
|
||||
this.update(state)
|
||||
},
|
||||
update: function(state){
|
||||
if (state) this.el.innerHTML = "x " + this.name
|
||||
else this.el.innerHTML = "_ " + this.name
|
||||
if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
|
||||
}
|
||||
})
|
||||
|
||||
var BlurredCheckbox = Checkbox.extend({
|
||||
focus: function(){
|
||||
this.use()
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var BlurredTool = Tool.extend({
|
||||
focus: function(){
|
||||
this.use()
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var HiddenCheckbox = BlurredCheckbox.extend({
|
||||
on: "o",
|
||||
off: ".",
|
||||
init: function (el){
|
||||
this.el = el
|
||||
this.lex = new Lex (el)
|
||||
this.name = this.el.id
|
||||
var state = localStorage.getItem("ascii.tools." + name) == "true" || this.el.innerHTML[0] == this.on
|
||||
this.update(state)
|
||||
},
|
||||
update: function(state){
|
||||
this.el.innerHTML = state ? this.on : this.off
|
||||
if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
|
||||
}
|
||||
})
|
@ -1,176 +0,0 @@
|
||||
var transform = (function(){
|
||||
|
||||
var p = [0,0], q = [0,0]
|
||||
var mode
|
||||
var copy
|
||||
|
||||
function down (e, lex, point){
|
||||
p[0] = point[0]
|
||||
p[1] = point[1]
|
||||
q[0] = e.pageX
|
||||
q[1] = e.pageY
|
||||
undo.new()
|
||||
undo.save_rect(0, 0, canvas.w, canvas.h)
|
||||
copy = canvas.clone()
|
||||
mode.init(e)
|
||||
}
|
||||
function move (e, lex, point){
|
||||
var pdx = point[0] - p[0]
|
||||
var pdy = point[1] - p[1]
|
||||
var dx = e.pageX - q[0]
|
||||
var dy = e.pageY - q[1]
|
||||
var w = canvas.w
|
||||
var h = canvas.h
|
||||
mode.before(dx, dy, pdx, pdy, point)
|
||||
for (var x = 0; x < w; x++) {
|
||||
for (var y = 0; y < h; y++) {
|
||||
lex = canvas.get(x, y)
|
||||
if (! mode.shade( copy, canvas, lex, x, y, w, h )) {
|
||||
lex.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function up (e){
|
||||
}
|
||||
|
||||
var modes = {
|
||||
|
||||
rotate: {
|
||||
init: function(e){
|
||||
mode.theta = 0
|
||||
},
|
||||
before: function(dx, dy){
|
||||
var radius = dist(0, 0, dx, dy)
|
||||
if (radius < 10) return
|
||||
mode.theta = angle(0, 0, dx, -dy)
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = (x/w) * 2 - 1
|
||||
y = (y/h) * 2 - 1
|
||||
var ca = cos(mode.theta)
|
||||
var sa = sin(mode.theta)
|
||||
var a = x * ca - y * sa
|
||||
var b = x * sa + y * ca
|
||||
x = (a + 1) / 2 * w
|
||||
y = (b + 1) / 2 * h
|
||||
var copy = src.get(x, y)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
scale: {
|
||||
init: function(e){
|
||||
mode.independent = e.shiftKey || e.altKey || e.metaKey
|
||||
mode.x_scale = mode.y_scale = 0
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
if (mode.independent) {
|
||||
mode.x_scale = Math.pow(2, -pdx / (canvas.w / 8))
|
||||
mode.y_scale = Math.pow(2, -pdy / (canvas.h / 8))
|
||||
}
|
||||
else {
|
||||
mode.x_scale = mode.y_scale = Math.pow(2, -pdx / (canvas.w / 8))
|
||||
}
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = ((x-p[0])/w) * 2 - 1
|
||||
y = ((y-p[1])/h) * 2 - 1
|
||||
x *= mode.x_scale
|
||||
y *= mode.y_scale
|
||||
x = (x + 1) / 2 * w
|
||||
y = (y + 1) / 2 * h
|
||||
var copy = src.get(x+p[0], y+p[1])
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
translate: {
|
||||
init: function(e){
|
||||
mode.dx = mode.dy = 0
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
mode.dx = -pdx
|
||||
mode.dy = -pdy
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
var copy = src.get(x+mode.dx, y+mode.dy)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
slice: {
|
||||
init: function(e){
|
||||
mode.is_y = ! (e.altKey || e.metaKey)
|
||||
mode.reverse = !! (e.shiftKey)
|
||||
mode.position = 0
|
||||
mode.direction = 0
|
||||
mode.last_dd = -1
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy, point){
|
||||
var new_position = mode.is_y ? point[1] : point[0]
|
||||
var dd = mode.is_y ? pdx : pdy
|
||||
|
||||
if (mode.position !== new_position) {
|
||||
mode.position = new_position
|
||||
mode.direction = 0
|
||||
}
|
||||
if (mode.last_dd !== -1) {
|
||||
mode.direction = mode.last_dd - dd
|
||||
}
|
||||
console.log(mode.position)
|
||||
mode.last_dd = dd
|
||||
copy.assign(canvas)
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
if (mode.is_y) {
|
||||
if (y >= mode.position || (mode.reverse && mode.position >= y)) {
|
||||
var copy = src.get(x + mode.direction, y)
|
||||
lex.assign(copy)
|
||||
}
|
||||
}
|
||||
else if (x >= mode.position || (mode.reverse && mode.position >= x)) {
|
||||
var copy = src.get(x, y + mode.direction)
|
||||
lex.assign(copy)
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
mode: {
|
||||
init: function(e){
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
function set_mode(m){
|
||||
if (m in modes) {
|
||||
mode = modes[m]
|
||||
transforming = true
|
||||
}
|
||||
}
|
||||
|
||||
function done(){
|
||||
transforming = false
|
||||
copy && copy.demolish()
|
||||
}
|
||||
|
||||
return {
|
||||
down: down,
|
||||
move: move,
|
||||
up: up,
|
||||
set_mode: set_mode,
|
||||
modes: modes,
|
||||
done: done,
|
||||
}
|
||||
|
||||
})()
|
@ -1,203 +0,0 @@
|
||||
var unicode = (function(){
|
||||
var UNICODE_BLOCK_LIST = [
|
||||
0x0020, 0x007F, "Basic Latin",
|
||||
0x0080, 0x00FF, "Latin-1 Supplement",
|
||||
0x2500, 0x257F, "Box Drawing",
|
||||
0x2580, 0x259F, "Block Elements",
|
||||
]
|
||||
var UNICODE_BLOCK_COUNT = UNICODE_BLOCK_LIST.length / 3
|
||||
var UNICODE_LOOKUP = {}
|
||||
for (var i = 0, len = UNICODE_BLOCK_LIST.length; i < len; i += 3) {
|
||||
UNICODE_LOOKUP[ UNICODE_BLOCK_LIST[i+2] ] = [ UNICODE_BLOCK_LIST[i], UNICODE_BLOCK_LIST[i+1] ]
|
||||
}
|
||||
|
||||
function block (name, n){
|
||||
var b = UNICODE_LOOKUP[name]
|
||||
if (! b) return ""
|
||||
return range.apply(null, b).map(function(n){ return String.fromCharCode(n) })
|
||||
}
|
||||
function entities (a) {
|
||||
return a.map(function(k){ return "&#" + k.join(";&#") + ";" }).join("<br>")
|
||||
}
|
||||
function index (j) {
|
||||
return [ UNICODE_BLOCK_LIST[j*3], UNICODE_BLOCK_LIST[j*3+1], UNICODE_BLOCK_LIST[j*3+2], [] ]
|
||||
}
|
||||
function range(m,n){
|
||||
if (m > n) return []
|
||||
var a = new Array (n-m)
|
||||
for (var i = 0, j = m; j <= n; i++, j++) {
|
||||
a[i] = j
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => '\xE3\x81\x82\xE3\x81\x84'
|
||||
// [ 0343, 0201, 0202, 0343, 0201, 0204 ] => '\343\201\202\343\201\204'
|
||||
function convertBytesToEscapedString(data_bytes, base) {
|
||||
var escaped = '';
|
||||
for (var i = 0; i < data_bytes.length; ++i) {
|
||||
var prefix = (base == 16 ? "\\x" : "\\");
|
||||
var num_digits = base == 16 ? 2 : 3;
|
||||
var escaped_byte = prefix + formatNumber(data_bytes[i], base, num_digits)
|
||||
escaped += escaped_byte;
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
// r'\xE3\x81\x82\xE3\x81\x84' => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ]
|
||||
// r'\343\201\202\343\201\204' => [ 0343, 0201, 0202, 0343, 0201, 0204 ]
|
||||
function convertEscapedBytesToBytes(str) {
|
||||
var parts = str.split("\\x");
|
||||
parts.shift(); // Trim the first element.
|
||||
var codes = [];
|
||||
var max = Math.pow(2, 8);
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
var code = parseInt(parts[i], 16);
|
||||
if (code >= 0 && code < max) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
// Malformed code ignored.
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => "ã‚ã„"
|
||||
function convertUnicodeCodePointsToString(unicode_codes) {
|
||||
var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes);
|
||||
return convertUtf16CodesToString(utf16_codes);
|
||||
}
|
||||
// 0x3042 => [ 0xE3, 0x81, 0x82 ]
|
||||
function convertUnicodeCodePointToUtf8Bytes(unicode_code) {
|
||||
var utf8_bytes = [];
|
||||
if (unicode_code < 0x80) { // 1-byte
|
||||
utf8_bytes.push(unicode_code);
|
||||
} else if (unicode_code < (1 << 11)) { // 2-byte
|
||||
utf8_bytes.push((unicode_code >>> 6) | 0xC0);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
} else if (unicode_code < (1 << 16)) { // 3-byte
|
||||
utf8_bytes.push((unicode_code >>> 12) | 0xE0);
|
||||
utf8_bytes.push(((unicode_code >> 6) & 0x3f) | 0x80);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
} else if (unicode_code < (1 << 21)) { // 4-byte
|
||||
utf8_bytes.push((unicode_code >>> 18) | 0xF0);
|
||||
utf8_bytes.push(((unicode_code >> 12) & 0x3F) | 0x80);
|
||||
utf8_bytes.push(((unicode_code >> 6) & 0x3F) | 0x80);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
}
|
||||
return utf8_bytes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ]
|
||||
// [ 0xD840, 0xDC0B ] => [ 0x2000B ] // A surrogate pair.
|
||||
function convertUnicodeCodePointsToUtf16Codes(unicode_codes) {
|
||||
var utf16_codes = [];
|
||||
for (var i = 0; i < unicode_codes.length; ++i) {
|
||||
var unicode_code = unicode_codes[i];
|
||||
if (unicode_code < (1 << 16)) {
|
||||
utf16_codes.push(unicode_code);
|
||||
} else {
|
||||
var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800;
|
||||
var second = (unicode_code % (1 << 10)) + 0xDC00;
|
||||
utf16_codes.push(first)
|
||||
utf16_codes.push(second)
|
||||
}
|
||||
}
|
||||
return utf16_codes;
|
||||
}
|
||||
// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => [ 0x3042, 0x3044 ]
|
||||
function convertUtf8BytesToUnicodeCodePoints(utf8_bytes) {
|
||||
var unicode_codes = [];
|
||||
var unicode_code = 0;
|
||||
var num_followed = 0;
|
||||
for (var i = 0; i < utf8_bytes.length; ++i) {
|
||||
var utf8_byte = utf8_bytes[i];
|
||||
if (utf8_byte >= 0x100) {
|
||||
// Malformed utf8 byte ignored.
|
||||
} else if ((utf8_byte & 0xC0) == 0x80) {
|
||||
if (num_followed > 0) {
|
||||
unicode_code = (unicode_code << 6) | (utf8_byte & 0x3f);
|
||||
num_followed -= 1;
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
} else {
|
||||
if (num_followed == 0) {
|
||||
unicode_codes.push(unicode_code);
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
if (utf8_byte < 0x80){ // 1-byte
|
||||
unicode_code = utf8_byte;
|
||||
num_followed = 0;
|
||||
} else if ((utf8_byte & 0xE0) == 0xC0) { // 2-byte
|
||||
unicode_code = utf8_byte & 0x1f;
|
||||
num_followed = 1;
|
||||
} else if ((utf8_byte & 0xF0) == 0xE0) { // 3-byte
|
||||
unicode_code = utf8_byte & 0x0f;
|
||||
num_followed = 2;
|
||||
} else if ((utf8_byte & 0xF8) == 0xF0) { // 4-byte
|
||||
unicode_code = utf8_byte & 0x07;
|
||||
num_followed = 3;
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_followed == 0) {
|
||||
unicode_codes.push(unicode_code);
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
unicode_codes.shift(); // Trim the first element.
|
||||
return unicode_codes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => "ã‚ã„"
|
||||
function convertUtf16CodesToString(utf16_codes) {
|
||||
var unescaped = '';
|
||||
for (var i = 0; i < utf16_codes.length; ++i) {
|
||||
unescaped += String.fromCharCode(utf16_codes[i]);
|
||||
}
|
||||
return unescaped;
|
||||
}
|
||||
// 0xff => "ff"
|
||||
// 0xff => "377"
|
||||
function formatNumber(number, base, num_digits) {
|
||||
var str = number.toString(base).toUpperCase();
|
||||
for (var i = str.length; i < num_digits; ++i) {
|
||||
str = "0" + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// encodes unicode characters as escaped bytes - \xFF
|
||||
// encodes ONLY non-ascii characters
|
||||
function escapeToEscapedBytes (txt) {
|
||||
var escaped_txt = "", kode, utf8_bytes
|
||||
for (var i = 0; i < txt.length; i++) {
|
||||
kode = txt.charCodeAt(i)
|
||||
if (kode > 0x7f) {
|
||||
utf8_bytes = convertUnicodeCodePointToUtf8Bytes(kode)
|
||||
escaped_txt += convertBytesToEscapedString(utf8_bytes, 16)
|
||||
}
|
||||
else {
|
||||
escaped_txt += txt[i]
|
||||
}
|
||||
}
|
||||
return escaped_txt
|
||||
}
|
||||
|
||||
// convert \xFF\xFF\xFF to unicode
|
||||
function unescapeFromEscapedBytes (str) {
|
||||
var data_bytes = convertEscapedBytesToBytes(str);
|
||||
var unicode_codes = convertUtf8BytesToUnicodeCodePoints(data_bytes);
|
||||
return convertUnicodeCodePointsToString(unicode_codes);
|
||||
}
|
||||
|
||||
return {
|
||||
raw: UNICODE_BLOCK_LIST,
|
||||
lookup: UNICODE_LOOKUP,
|
||||
index: index,
|
||||
range: range,
|
||||
block: block,
|
||||
escapeToEscapedBytes: escapeToEscapedBytes,
|
||||
unescapeFromEscapedBytes: unescapeFromEscapedBytes,
|
||||
}
|
||||
})()
|
@ -1,192 +0,0 @@
|
||||
if (window.$) {
|
||||
$.fn.int = function(){ return parseInt($(this).val(),10) }
|
||||
$.fn.float = function(){ return parseFloat($(this).val()) }
|
||||
$.fn.string = function(){ return trim($(this).val()) }
|
||||
$.fn.enable = function() { return $(this).attr("disabled",null) }
|
||||
$.fn.disable = function() { return $(this).attr("disabled","disabled") }
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
function trim(s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
|
||||
|
||||
var E = Math.E
|
||||
var PI = Math.PI
|
||||
var PHI = (1+Math.sqrt(5))/2
|
||||
var TWO_PI = PI*2
|
||||
var LN10 = Math.LN10
|
||||
function clamp(n,a,b){ return n<a?a:n<b?n:b }
|
||||
function norm(n,a,b){ return (n-a) / (b-a) }
|
||||
function lerp(n,a,b){ return (b-a)*n+a }
|
||||
function mix(n,a,b){ return a*(1-n)+b*n }
|
||||
function ceil(n){ return Math.ceil(n) }
|
||||
function floor(n){ return Math.floor(n) }
|
||||
function round(n){ return Math.round(n) }
|
||||
function max(a,b){ return Math.max(a,b) }
|
||||
function min(a,b){ return Math.min(a,b) }
|
||||
function abs(n){ return Math.abs(n) }
|
||||
function sign(n){ return Math.abs(n)/n }
|
||||
function pow(n,b) { return Math.pow(n,b) }
|
||||
function exp(n) { return Math.exp(n) }
|
||||
function log(n){ return Math.log(n) }
|
||||
function ln(n){ return Math.log(n)/LN10 }
|
||||
function sqrt(n) { return Math.sqrt(n) }
|
||||
function cos(n){ return Math.cos(n) }
|
||||
function sin(n){ return Math.sin(n) }
|
||||
function tan(n){ return Math.tan(n) }
|
||||
function acos(n){ return Math.cos(n) }
|
||||
function asin(n){ return Math.sin(n) }
|
||||
function atan(n){ return Math.atan(n) }
|
||||
function atan2(a,b){ return Math.atan2(a,b) }
|
||||
function sec(n){ return 1/cos(n) }
|
||||
function csc(n){ return 1/sin(n) }
|
||||
function cot(n){ return 1/tan(n) }
|
||||
function cosp(n){ return (1+Math.cos(n))/2 } // cos^2
|
||||
function sinp(n){ return (1+Math.sin(n))/2 }
|
||||
function random(){ return Math.random() }
|
||||
function rand(n){ return (Math.random()*n) }
|
||||
function randint(n){ return rand(n)|0 }
|
||||
function randrange(a,b){ return a + rand(b-a) }
|
||||
function randsign(){ return random() >= 0.5 ? -1 : 1 }
|
||||
function randnullsign(){ var r = random(); return r < 0.333 ? -1 : r < 0.666 ? 0 : 1 }
|
||||
|
||||
function xrandom(exp){ return Math.pow(Math.random(), exp) }
|
||||
function xrand(exp,n){ return (xrandom(exp)*n) }
|
||||
function xrandint(exp,n){ return rand(exp,n)|0 }
|
||||
function xrandrange(exp,a,b){ return a + xrand(exp,b-a) }
|
||||
|
||||
function choice(a){ return a[randint(a.length)] }
|
||||
function deg(n){ return n*180/PI }
|
||||
function rad(n){ return n*PI/180 }
|
||||
function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
|
||||
function mod(n,m){ n = n % m; return n < 0 ? (m + n) : n }
|
||||
function modi(n,m){ return floor(mod(n,m)) }
|
||||
function dist(x0,y0,x1,y1){ return sqrt(pow(x1-x0,2)+pow(y1-y0,2)) }
|
||||
function angle(x0,y0,x1,y1){ return atan2(y1-y0,x1-x0) }
|
||||
function avg(m,n,a){ return (m*(a-1)+n)/a }
|
||||
function quantize(a,b){ return floor(a/b)*b }
|
||||
function quantile(a,b){ return floor(a/b) }
|
||||
|
||||
function pixel(x,y){ return 4*(mod(y,actual_h)*actual_w+mod(x,actual_w)) }
|
||||
function rgbpixel(d,x,y){
|
||||
var p = pixel(~~x,~~y)
|
||||
r = d[p]
|
||||
g = d[p+1]
|
||||
b = d[p+2]
|
||||
a = d[p+3]
|
||||
}
|
||||
function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) }
|
||||
|
||||
function step(a, b){
|
||||
return (b >= a) + 0
|
||||
// ^^ bool -> int
|
||||
}
|
||||
|
||||
function julestep (a,b,n) {
|
||||
return clamp(norm(n,a,b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// hermite curve apparently
|
||||
function smoothstep(min,max,n){
|
||||
var t = clamp((n - min) / (max - min), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
function toArray(a){ return Array.prototype.slice.call(a) }
|
||||
function shuffle(a){
|
||||
for (var i = a.length; i > 0; i--){
|
||||
var r = randint(i)
|
||||
var swap = a[i-1]
|
||||
a[i-1] = a[r]
|
||||
a[r] = swap
|
||||
}
|
||||
return a
|
||||
}
|
||||
function reverse(a){
|
||||
var reversed = []
|
||||
for (var i = 0, _len = a.length-1; i <= _len; i++){
|
||||
reversed[i] = a[_len-i]
|
||||
}
|
||||
return reversed
|
||||
}
|
||||
function deinterlace(a){
|
||||
var odd = [], even = []
|
||||
for (var i = 0, _len = a.length; i < _len; i++) {
|
||||
if (i % 2) even.push(a[i])
|
||||
else odd.push(a[i])
|
||||
}
|
||||
return [even, odd]
|
||||
}
|
||||
function weave(a){
|
||||
var aa = deinterlace(a)
|
||||
var b = []
|
||||
aa[0].forEach(function(el){ b.push(el) })
|
||||
reverse(aa[1]).forEach(function(el){ b.push(el) })
|
||||
return b
|
||||
}
|
||||
function cssRule (selector, declaration) {
|
||||
var x = document.styleSheets, y = x.length-1;
|
||||
x[y].insertRule(selector+"{"+declaration+"}", x[y].cssRules.length);
|
||||
}
|
||||
|
||||
// easing functions
|
||||
function circular (t) { return Math.sqrt( 1 - ( --t * t ) ) }
|
||||
function quadratic (t) { return t * ( 2 - t ) }
|
||||
function back (t) {
|
||||
var b = 4;
|
||||
return ( t = t - 1 ) * t * ( ( b + 1 ) * t + b ) + 1;
|
||||
}
|
||||
function bounce (t) {
|
||||
if (t >= 1) return 1;
|
||||
if ( ( t /= 1 ) < ( 1 / 2.75 ) ) {
|
||||
return 7.5625 * t * t;
|
||||
} else if ( t < ( 2 / 2.75 ) ) {
|
||||
return 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75;
|
||||
} else if ( t < ( 2.5 / 2.75 ) ) {
|
||||
return 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375;
|
||||
} else {
|
||||
return 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375;
|
||||
}
|
||||
}
|
||||
function elastic (t) {
|
||||
var f = 0.22,
|
||||
e = 0.4;
|
||||
|
||||
if ( t === 0 ) { return 0; }
|
||||
if ( t == 1 ) { return 1; }
|
||||
|
||||
return ( e * Math.pow( 2, - 10 * t ) * Math.sin( ( t - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
|
||||
}
|
||||
|
||||
Model=function a(b,c,d,e){function f(){var a=this,f={};a.on=function(a,b){(f[a]||
|
||||
(f[a]=[])).push(b)},a.trigger=function(a,b){for(var c=f[a],d=0;c&&d<c.length;)c
|
||||
[d++](b)},a.off=function(a,b){for(d=f[a]||[];b&&(c=d.indexOf(b))>-1;)d.splice(c
|
||||
,1);f[a]=b?d:[]};for(c in b)d=b[c],a[c]=typeof d=="function"?function(){return(
|
||||
d=this.apply(a,arguments))===e?a:d}.bind(d):d;a.init&&a.init.apply(a,arguments)
|
||||
}return f.extend=function(f){d={};for(c in b)d[c]=b[c];for(c in f)d[c]=f[c],b[c
|
||||
]!==e&&(d["__"+c]=b[c]);return a(d)},f},typeof module=="object"&&(module.exports
|
||||
=Model); // c-{{{-<
|
||||
|
||||
function defaults (dest, src) {
|
||||
dest = dest || {}
|
||||
for (var i in src) {
|
||||
dest[i] = typeof dest[i] == 'undefined' ? src[i] : dest[i]
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
function setSelectionRange(input, selectionStart, selectionEnd) {
|
||||
if (input.setSelectionRange) {
|
||||
input.focus();
|
||||
input.setSelectionRange(selectionStart, selectionEnd);
|
||||
}
|
||||
else if (input.createTextRange) {
|
||||
var range = input.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', selectionEnd);
|
||||
range.moveStart('character', selectionStart);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
function setCaretToPos(input, pos) {
|
||||
setSelectionRange(input, pos, pos);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
RELEASE_DEPS="rsync sed";
|
||||
|
||||
rc() {
|
||||
local _cmd="${1}"; shift;
|
||||
printf "%s >>> %s %s\n" "$(date +"%d-%^b-%Y %H:%M:%S")" "${_cmd}" "${*}";
|
||||
"${_cmd}" "${@}";
|
||||
};
|
||||
|
||||
usage() {
|
||||
echo "usage: ${0} [-h] old_version new_version" >&2;
|
||||
echo " -h.........: show this screen" >&2;
|
||||
};
|
||||
|
||||
main() {
|
||||
local _opt="" _version_new="" _version_new_code="" _version_old="";
|
||||
while getopts hv _opt; do
|
||||
case "${_opt}" in
|
||||
h) usage; exit 0; ;;
|
||||
*) usage; exit 1; ;;
|
||||
esac; done;
|
||||
shift $((${OPTIND}-1));
|
||||
if [ -z "${1}" ]; then
|
||||
echo "error: empty or missing old version number argument" >&2; usage; exit 1;
|
||||
elif [ -z "${2}" ]; then
|
||||
echo "error: empty or missing new version number argument" >&2; usage; exit 1;
|
||||
else
|
||||
_version_old="${1}"; _version_new="${2}";
|
||||
_version_new_code="$(echo "${_version_new}" | sed -e 's,\.,,g' -e 's/^[0-9]/&00/')";
|
||||
fi;
|
||||
for _cmd in ${RELEASE_DEPS_CMD}; do
|
||||
if ! which "${_cmd}" >/dev/null; then
|
||||
echo "error: missing prerequisite command \`${_cmd}'";
|
||||
exit 1;
|
||||
fi;
|
||||
done;
|
||||
rc sed -i"" '/version="/s/\(version=\)"'"${_version_old}"'"/\1"'"${_version_new}"'"/' \
|
||||
MiRCART-cordoba/config.xml;
|
||||
rc sed -i"" '/"version":/s/\("version":\s*\)"'"${_version_old}"'"/\1"'"${_version_new}"'"/' \
|
||||
MiRCART-cordoba/package.json \
|
||||
MiRCART-cordoba/package-lock.json;
|
||||
rc sed -i"" '/android:versionCode="/s/\(android:versionCode=\)"[0-9]\+"/\1"'"${_version_new_code}"'"/' \
|
||||
MiRCART-cordoba/platforms/android/app/src/main/AndroidManifest.xml;
|
||||
rc sed -i"" '/android:versionName="/s/\(android:versionName=\)"'"${_version_old}"'"/\1"'"${_version_new}"'"/' \
|
||||
MiRCART-cordoba/platforms/android/app/src/main/AndroidManifest.xml;
|
||||
rc sed -i"" '/version="/s/\(version=\)"'"${_version_old}"'"/\1"'"${_version_new}"'"/' \
|
||||
MiRCART-cordoba/platforms/android/app/src/main/res/xml/config.xml;
|
||||
rc sed -i"" '/"version":/s/\("version":\s*\)"'"${_version_old}"'"/\1"'"${_version_new}"'"/' \
|
||||
MiRCART-nw/package.json \
|
||||
MiRCART-nw/package-lock.json;
|
||||
rc sed -i"" '/<title>[^<]\+ v/s/\(<title>[^<]\+ v\)'"${_version_old}"'\(.*<\)/\1'"${_version_new}"'\2/' \
|
||||
index.html assets/html/help.html;
|
||||
rc rsync -aiLPv --delete \
|
||||
MiRCART-cordoba/www/ \
|
||||
MiRCART-cordoba/platforms/android/app/src/main/assets/www/;
|
||||
rc git add MiRCART-cordoba/platforms/android/app/src/main/assets/www;
|
||||
rc git commit -avm "Bump to v${_version_new}.";
|
||||
};
|
||||
|
||||
set -o errexit -o noglob;
|
||||
main "${@}";
|
||||
|
||||
# vim:foldmethod=marker sw=8 ts=8 tw=120
|
@ -1,40 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
msgf() {
|
||||
local _fmt="${1}"; shift;
|
||||
printf "%s >>> ${_fmt}\n" "$(date +"%d-%^b-%Y %H:%M:%S")" "${@}";
|
||||
};
|
||||
|
||||
usage() {
|
||||
echo "usage: ${0} [-h] [-v]" >&2;
|
||||
echo " -h.........: show this screen" >&2;
|
||||
echo " -v.........: be verbose" >&2;
|
||||
};
|
||||
|
||||
main() {
|
||||
local _cmd="" _build="" _opt="" _vflag=0;
|
||||
while getopts hv _opt; do
|
||||
case "${_opt}" in
|
||||
h) usage; exit 0; ;;
|
||||
v) _vflag=1; ;;
|
||||
*) usage; exit 1; ;;
|
||||
esac; done;
|
||||
shift $((${OPTIND}-1));
|
||||
for _build in cordoba nw www; do
|
||||
msgf "Deploying ${_build}...";
|
||||
cd "MiRCART-${_build}";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
./deploy.sh "${@}";
|
||||
else
|
||||
./deploy.sh -v "${@}";
|
||||
fi;
|
||||
cd "${OLDPWD}";
|
||||
msgf "Deployed ${_build}.";
|
||||
done;
|
||||
};
|
||||
|
||||
set -o errexit -o noglob;
|
||||
main "${@}";
|
||||
|
||||
# vim:foldmethod=marker sw=8 ts=8 tw=120
|
@ -1,79 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
CORDOBA_PLATFORMS="android";
|
||||
PACKAGE_NAME="MiRCART-cordoba";
|
||||
RELEASE_DEPS_CMD="cordova cp";
|
||||
RELEASE_DEPS_ENV="ANDROID_HOME JAVA_HOME";
|
||||
RELEASES_DNAME_DEST="releases";
|
||||
RELEASES_DNAME_SRC="platforms/%CORDOBA_PLATFORM%/app/build/outputs/apk";
|
||||
|
||||
msgf() {
|
||||
local _fmt="${1}"; shift;
|
||||
printf "%s >>> ${_fmt}\n" "$(date +"%d-%^b-%Y %H:%M:%S")" "${@}";
|
||||
};
|
||||
|
||||
subst() {
|
||||
local _string="${1}" _search="${2}" _replace="${3}" _string_="";
|
||||
_string_="${_string%${_search}*}";
|
||||
_string_="${_string_}${_replace}"
|
||||
_string_="${_string_}${_string#*${_search}}";
|
||||
echo "${_string_}";
|
||||
};
|
||||
|
||||
deploy() {
|
||||
local _platform="${1}" _vflag="${2}" _release_dname_src="" _release_type="" _release_version="";
|
||||
|
||||
_release_dname_src="$(subst "${RELEASES_DNAME_SRC}" "%CORDOBA_PLATFORM%" "${_platform}")";
|
||||
_release_version="$(sed -n '/^\s*"version":/s/^.*:\s*"\([0-9.]\+\)",\?\s*$/\1/p' package.json)";
|
||||
for _release_type in debug release; do
|
||||
cordova build "--${_release_type}" --device "${_platform}";
|
||||
cp -a "$(find "${_release_dname_src}/${_release_type}" -name \*.apk)" \
|
||||
"${RELEASES_DNAME_DEST}/${PACKAGE_NAME}-${_release_type}-${_release_version}.apk";
|
||||
done;
|
||||
};
|
||||
|
||||
usage() {
|
||||
echo "usage: ${0} [-h] [-v] [platform...]" >&2;
|
||||
echo " -h.........: show this screen" >&2;
|
||||
echo " -v.........: be verbose" >&2;
|
||||
echo " platform...: one of: \`${CORDOBA_PLATFORMS}'" >&2;
|
||||
};
|
||||
|
||||
main() {
|
||||
local _cmd="" _opt="" _platform="" _platforms="" _vflag=0 _vname="";
|
||||
while getopts hv _opt; do
|
||||
case "${_opt}" in
|
||||
h) usage; exit 0; ;;
|
||||
v) _vflag=1; ;;
|
||||
*) usage; exit 1; ;;
|
||||
esac; done;
|
||||
shift $((${OPTIND}-1));
|
||||
for _cmd in ${RELEASE_DEPS_CMD}; do
|
||||
if ! which "${_cmd}" >/dev/null; then
|
||||
echo "error: missing prerequisite command \`${_cmd}'";
|
||||
exit 1;
|
||||
fi;
|
||||
done;
|
||||
for _vname in ${RELEASE_DEPS_ENV}; do
|
||||
if [ -z "$(eval echo \"\${_vname}\")" ]; then
|
||||
echo "error: missing prerequisite environment variable \`${_vname}'";
|
||||
exit 1;
|
||||
fi;
|
||||
done;
|
||||
_platforms="${@}"; mkdir -p "${RELEASES_DNAME_DEST}";
|
||||
for _platform in ${_platforms:-${CORDOBA_PLATFORMS}}; do
|
||||
msgf "Building ${_platform} release...";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
deploy "${_platform}" "${_vflag}" >/dev/null;
|
||||
else
|
||||
deploy "${_platform}" "${_vflag}";
|
||||
fi;
|
||||
msgf "Built ${_platform} release.";
|
||||
done;
|
||||
};
|
||||
|
||||
set -o errexit -o noglob;
|
||||
main "${@}";
|
||||
|
||||
# vim:foldmethod=marker sw=8 ts=8 tw=120
|
@ -1,121 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
PACKAGE_NAME="MiRCART-nw";
|
||||
RELEASE_DEPS="cpio find gunzip rm sed tar unzip wget zip";
|
||||
NWJS_MANIFEST_FNAME="nwjs.manifest";
|
||||
NWJS_PLATFORMS="linux-ia32 linux-x64 win-ia32 win-x64";
|
||||
NWJS_VERSION="0.34.5";
|
||||
NWJS_SUBDIR="nwjs-v${NWJS_VERSION}-%NWJS_PLATFORM%";
|
||||
NWJS_URL_linux="https://dl.nwjs.io/v${NWJS_VERSION}/nwjs-v${NWJS_VERSION}-%NWJS_PLATFORM%.tar.gz";
|
||||
NWJS_URL_win="https://dl.nwjs.io/v${NWJS_VERSION}/nwjs-v${NWJS_VERSION}-%NWJS_PLATFORM%.zip";
|
||||
RELEASES_DNAME="releases";
|
||||
|
||||
extract() {
|
||||
local _fname="${1}" _dest_dname="${2}";
|
||||
if [ -n "${_fname}" -a -z "${_fname##*.tar.gz}" ]; then
|
||||
tar -C "${_dest_dname}" -xpf "${_fname}";
|
||||
elif [ -n "${_fname}" -a -z "${_fname##*.zip}" ]; then
|
||||
unzip -d "${_dest_dname}" "${_fname}";
|
||||
else
|
||||
echo "error: file \`${_fname}' is of unknown archive type" >&2; exit 1;
|
||||
fi;
|
||||
};
|
||||
|
||||
msgf() {
|
||||
local _fmt="${1}"; shift;
|
||||
printf "%s >>> ${_fmt}\n" "$(date +"%d-%^b-%Y %H:%M:%S")" "${@}";
|
||||
};
|
||||
|
||||
subst() {
|
||||
local _string="${1}" _search="${2}" _replace="${3}" _string_="";
|
||||
_string_="${_string%${_search}*}";
|
||||
_string_="${_string_}${_replace}"
|
||||
_string_="${_string_}${_string#*${_search}}";
|
||||
echo "${_string_}";
|
||||
};
|
||||
|
||||
deploy() {
|
||||
local _platform="${1}" _vflag="${2}" _nwjs_fname="" _nwjs_subdir="" _nwjs_url="" \
|
||||
_release_fname="" _release_dname="" _release_version="";
|
||||
|
||||
_nwjs_subdir="$(subst "${NWJS_SUBDIR}" "%NWJS_PLATFORM%" "${_platform}")";
|
||||
_nwjs_url="$(subst "$(eval echo \"\${NWJS_URL_${_platform%%-*}}\")" "%NWJS_PLATFORM%" "${_platform}")";
|
||||
_nwjs_fname="${RELEASES_DNAME}/${_nwjs_url##*/}";
|
||||
_release_version="$(sed -n '/^\s*"version":/s/^.*:\s*"\([0-9.]\+\)",\?\s*$/\1/p' package.json)";
|
||||
_release_dname="${RELEASES_DNAME}/${PACKAGE_NAME}-release-${_platform}-${_release_version}";
|
||||
_release_fname="${_release_dname}.zip";
|
||||
|
||||
trap "rm -fr ${_release_dname}" EXIT HUP INT QUIT PIPE TERM USR1 USR2;
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
wget -cqO "${_nwjs_fname}" "${_nwjs_url}";
|
||||
else
|
||||
wget -cO "${_nwjs_fname}" "${_nwjs_url}";
|
||||
fi;
|
||||
if ! sha256sum --ignore-missing -c --status "${NWJS_MANIFEST_FNAME}"; then
|
||||
echo "error: SHA256 sum mismatch for \`${_nwjs_fname}'" >&2; return 1;
|
||||
fi;
|
||||
rm -rf "${_release_dname}"; mkdir -p "${_release_dname}"; extract "${_nwjs_fname}" "${_release_dname}";
|
||||
|
||||
cd "${_release_dname}/${_nwjs_subdir}";
|
||||
find . \
|
||||
-mindepth 1 |\
|
||||
cpio --quiet -dmp ..;
|
||||
cd "${OLDPWD}";
|
||||
rm -fr "${_release_dname}/${_nwjs_subdir}";
|
||||
find -L . \
|
||||
-mindepth 1 \
|
||||
-not -path "./${RELEASES_DNAME}/*" \
|
||||
-not -path "./${RELEASES_DNAME}" \
|
||||
-not -name '*.sw*' \
|
||||
-not -name "${0##*/}" \
|
||||
-not -name "${NWJS_MANIFEST_FNAME}" |\
|
||||
cpio --quiet -dLmp "${_release_dname}";
|
||||
cd "${RELEASES_DNAME}";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
zip -9 -r "${_release_fname##${RELEASES_DNAME}/}" "${_release_dname##${RELEASES_DNAME}/}" >/dev/null;
|
||||
else
|
||||
zip -9 -r "${_release_fname##${RELEASES_DNAME}/}" "${_release_dname##${RELEASES_DNAME}/}";
|
||||
fi;
|
||||
cd "${OLDPWD}"; rm -fr "${_release_dname}";
|
||||
trap - EXIT HUP INT QUIT PIPE TERM USR1 USR2;
|
||||
};
|
||||
|
||||
usage() {
|
||||
echo "usage: ${0} [-h] [-v] [platform...]" >&2;
|
||||
echo " -h.........: show this screen" >&2;
|
||||
echo " -v.........: be verbose" >&2;
|
||||
echo " platform...: one of: \`${NWJS_PLATFORMS}'" >&2;
|
||||
};
|
||||
|
||||
main() {
|
||||
local _cmd="" _opt="" _platform="" _platforms="" _vflag=0;
|
||||
while getopts hv _opt; do
|
||||
case "${_opt}" in
|
||||
h) usage; exit 0; ;;
|
||||
v) _vflag=1; ;;
|
||||
*) usage; exit 1; ;;
|
||||
esac; done;
|
||||
shift $((${OPTIND}-1));
|
||||
for _cmd in ${RELEASE_DEPS}; do
|
||||
if ! which "${_cmd}" >/dev/null; then
|
||||
echo "error: missing prerequisite command \`${_cmd}'";
|
||||
exit 1;
|
||||
fi;
|
||||
done;
|
||||
_platforms="${@}"; mkdir -p "${RELEASES_DNAME}";
|
||||
for _platform in ${_platforms:-${NWJS_PLATFORMS}}; do
|
||||
msgf "Building ${_platform} release...";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
deploy "${_platform}" "${_vflag}" >/dev/null;
|
||||
else
|
||||
deploy "${_platform}" "${_vflag}";
|
||||
fi;
|
||||
msgf "Built ${_platform} release.";
|
||||
done;
|
||||
};
|
||||
|
||||
set -o errexit -o noglob;
|
||||
main "${@}";
|
||||
|
||||
# vim:foldmethod=marker sw=8 ts=8 tw=120
|
@ -1,69 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
PACKAGE_NAME="MiRCART-www";
|
||||
RELEASE_DEPS="cpio find rm sed zip";
|
||||
RELEASES_DNAME="releases";
|
||||
|
||||
msgf() {
|
||||
local _fmt="${1}"; shift;
|
||||
printf "%s >>> ${_fmt}\n" "$(date +"%d-%^b-%Y %H:%M:%S")" "${@}";
|
||||
};
|
||||
|
||||
deploy() {
|
||||
local _vflag="${1}" _release_fname="" _release_dname="" _release_version="";
|
||||
|
||||
_release_version="$(sed -n '/^\s*<title>/s/^\s*<title>MiRCART v\([0-9.]\+\)<\/title>\s*$/\1/p' index.html)";
|
||||
_release_dname="${RELEASES_DNAME}/${PACKAGE_NAME}-${_release_version}";
|
||||
_release_fname="${_release_dname}.zip";
|
||||
|
||||
find -L . \
|
||||
-mindepth 1 \
|
||||
-not -path "./${RELEASES_DNAME}/*" \
|
||||
-not -path "./${RELEASES_DNAME}" \
|
||||
-not -name '*.sw*' \
|
||||
-not -name "${0##*/}" |\
|
||||
cpio --quiet -dLmp "${_release_dname}";
|
||||
cd "${RELEASES_DNAME}";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
zip -9 -r "${_release_fname##${RELEASES_DNAME}/}" "${_release_dname##${RELEASES_DNAME}/}" >/dev/null;
|
||||
else
|
||||
zip -9 -r "${_release_fname##${RELEASES_DNAME}/}" "${_release_dname##${RELEASES_DNAME}/}";
|
||||
fi;
|
||||
cd "${OLDPWD}"; rm -fr "${_release_dname}";
|
||||
};
|
||||
|
||||
usage() {
|
||||
echo "usage: ${0} [-h] [-v]" >&2;
|
||||
echo " -h.........: show this screen" >&2;
|
||||
echo " -v.........: be verbose" >&2;
|
||||
};
|
||||
|
||||
main() {
|
||||
local _cmd="" _opt="" _vflag=0;
|
||||
while getopts hv _opt; do
|
||||
case "${_opt}" in
|
||||
h) usage; exit 0; ;;
|
||||
v) _vflag=1; ;;
|
||||
*) usage; exit 1; ;;
|
||||
esac; done;
|
||||
shift $((${OPTIND}-1));
|
||||
for _cmd in ${RELEASE_DEPS}; do
|
||||
if ! which "${_cmd}" >/dev/null; then
|
||||
echo "error: missing prerequisite command \`${_cmd}'";
|
||||
exit 1;
|
||||
fi;
|
||||
done;
|
||||
msgf "Building release...";
|
||||
if [ "${_vflag:-0}" -eq 0 ]; then
|
||||
deploy "${_vflag}" >/dev/null;
|
||||
else
|
||||
deploy "${_vflag}";
|
||||
fi;
|
||||
msgf "Built release.";
|
||||
};
|
||||
|
||||
set -o errexit -o noglob;
|
||||
main "${@}";
|
||||
|
||||
# vim:foldmethod=marker sw=8 ts=8 tw=120
|
@ -1,19 +0,0 @@
|
||||
Copyright (c) 2018 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,28 +0,0 @@
|
||||
Jollo LNT license
|
||||
Version 1 - February 2015
|
||||
|
||||
Copyright, 2015. JOLLO NET NA.
|
||||
The Jollo IRC Network. <//jollo.org/>
|
||||
|
||||
Vu, fare wanderer, confronted with raw, programmatic instruction
|
||||
dans la forme la plus pure. A hesitation, troubled to the terms
|
||||
qui ce license affirme. Par un voyage du explorer le mechanisme
|
||||
et ponder la fabrication. Voila! La remide: egress sans risque.
|
||||
|
||||
Sans trace (Leave No Trace) via sept principales:
|
||||
|
||||
0. Modifique language en advance. L'Apposer Jollo LNT license
|
||||
with copies en distribuer.
|
||||
|
||||
1. Non responsible pour neglige programme du problematique.
|
||||
|
||||
2. Non sympathie pour neglige programme du problematique.
|
||||
|
||||
3. Non permission l'modifique under any circumstance.
|
||||
|
||||
4. Non permission distribution under any circumstance.
|
||||
|
||||
5. Respect les programmatic instructions.
|
||||
|
||||
6. Non interfere avec l'harmonie d'une amitie.
|
||||
|
@ -1,15 +0,0 @@
|
||||
## MiRCART-{cordoba,nw,www} -- mIRC art editor for {Android,{Linux,Windows},browsers} (WIP)
|
||||
Originally based on [[1](#r1)].
|
||||
|
||||
## IrcMiRCARTBot.py -- IRC<->MiRC2png bot (for EFnet #MiRCART) (pending cleanup)
|
||||
* Prerequisites: python3 && python3-{json,requests,urllib3} on Debian-family Linux distributions
|
||||
* IrcMiRCARTBot.py usage: IrcMiRCARTBot.py `<IRC server hostname>` [`<IRC server port; defaults to 6667>`] [`<IRC bot nick name; defaults to pngbot>`] [`<IRC bot user name; defaults to pngbot>`] [`<IRC bot real name; defaults to pngbot>`] [`<IRC bot channel name; defaults to #MiRCART>`]
|
||||
|
||||
## MiRCARTToPngFile.py -- convert ASCII w/ mIRC control codes to monospaced PNG (pending cleanup)
|
||||
* Prerequisites: python3 && python3-pil on Debian-family Linux distributions
|
||||
* MiRC2png.py usage: MiRC2png.py `<MiRCART input file pathname>` `<PNG image output file pathname>` [`<Font file pathname; defaults to DejaVuSansMono.ttf>`] [`<Font size; defaults to 11>`]
|
||||
|
||||
## References
|
||||
``Fri, 05 Jan 2018 17:01:47 +0100 [1]`` <a href="https://asdf.us/asciiblaster" id="r1">asciiblaster</a>
|
||||
|
||||
vim:tw=0
|
@ -1,20 +0,0 @@
|
||||
1) feature: animation; Blender mIRCart Python importer plugin
|
||||
2) feature: {bold,italic,underline} attributes
|
||||
3) feature: draw w/ keyboard instead of [LR]MB
|
||||
4) feature: {line,measuring} tool
|
||||
5) feature: multiplayer realtime editing
|
||||
6) feature: status bar w/ position, etc.
|
||||
7) feature: zoom {in,out} (esp. on Android)
|
||||
8) reimplement: ANSI (maybe,) {save,upload} as PNG & gallery, cutoff (print line #(s) affected, tunably,) shaders (maybe)
|
||||
9) releases: provide signed release APK
|
||||
|
||||
In progress:
|
||||
1) each newly created & modified(!) ascii is assigned an ascii UUID, if not present already
|
||||
2) each ascii w/ an ascii UUID is auto-saved to localStorage every 3 minutes
|
||||
3) ascii autosaves w/ count >8 (or >8*3mins=24mins old) are purged from localStorage
|
||||
4) asciis w/ count >8 are purged from localStorage
|
||||
5) limits are tunable and either defaulted or localStorage'd
|
||||
6) UI lists: a) asciis (with UUID) b) autosaves c) parameters
|
||||
7) always prompt on close window/exit/...
|
||||
8) always prompt on new
|
||||
9) save to text file @ standalone app
|
@ -1,116 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<!-- {{{ HEAD -->
|
||||
<head>
|
||||
<link charset="utf-8" href="assets/css/ak.css" rel="stylesheet" type="text/css" />
|
||||
<link charset="utf-8" href="assets/css/fonts.css" rel="stylesheet" type="text/css" />
|
||||
<link charset="utf-8" href="assets/css/sally.css" rel="stylesheet" type="text/css" />
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
<meta content="width=device-width, maximum-scale=1.0, user-scalable=yes" name="viewport" />
|
||||
<title>MiRCART v1.1.0</title>
|
||||
</head>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ BODY -->
|
||||
<body class="loading initial">
|
||||
<!-- {{{ DIV: workspace_wrapper -->
|
||||
<div id="workspace_wrapper">
|
||||
<div id="canvas_wrapper" class="wrapper"></div>
|
||||
</div>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ DIV: ui_wrapper -->
|
||||
<div id="ui_wrapper">
|
||||
<!-- {{{ DIV: tools_block -->
|
||||
<div class="block" id="tools_block">
|
||||
<div id="palette_wrapper"></div>
|
||||
<div id="secret_wrapper">
|
||||
<span id="experimental_palette_toggle">.</span>
|
||||
</div>
|
||||
<div id="letters_wrapper"></div>
|
||||
<div id="custom_wrapper"></div>
|
||||
</div>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ DIV: brush_container -->
|
||||
<div id="brush_container" class="block">
|
||||
<div id="brush_wrapper"></div>
|
||||
<br />
|
||||
<span id="fg_checkbox" class="tool">x fg</span><br />
|
||||
<span id="bg_checkbox" class="tool">x bg</span><br />
|
||||
<span id="char_checkbox" class="tool">x char</span><br />
|
||||
<span id="add_custom_el" class="tool">+ add</span><br />
|
||||
<br />
|
||||
<span id="undo_el" class="tool hidden">undo</span><br />
|
||||
<span id="redo_el" class="tool hidden">redo</span><br />
|
||||
</div>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ DIV: tools_wrapper -->
|
||||
<div id="tools_wrapper" class="block">
|
||||
<span id="square_el" class="tool">square</span><br />
|
||||
<span id="circle_el" class="tool">circle</span><br />
|
||||
<span id="cross_el" class="tool">cross</span><br />
|
||||
<span id="text_el" class="tool">text</span><br />
|
||||
<span id="fill_el" class="tool">fill</span><br />
|
||||
<span id="select_el" class="tool">select</span><br />
|
||||
<br />
|
||||
<span id="rotate_el" class="tool">rotate</span><br />
|
||||
<span id="scale_el" class="tool">scale</span><br />
|
||||
<span id="translate_el" class="tool">translate</span><br />
|
||||
<span id="slice_el" class="tool">slice</span><br />
|
||||
</div>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ DIV: textarea_mode -->
|
||||
<div id="textarea_mode">
|
||||
<div>
|
||||
<span id="clear_el" class="tool">new</span>
|
||||
<span id="save_el" class="tool">save</span>
|
||||
<span id="load_el" class="tool">load</span>
|
||||
<a id="doc_el" href="assets/html/help.html" target="_blank">help</a>
|
||||
<br />
|
||||
brush: <span id="brush_w_el" class="ed">5</span> x <span id="brush_h_el" class="ed">5</span><br />
|
||||
canvas: <span id="canvas_w_el" class="ed">100</span> x <span id="canvas_h_el" class="ed">30</span><br />
|
||||
<span id="grid_el" class="tool">_ grid</span>
|
||||
<span id="vertical_checkbox" class="tool">x vertical</span>
|
||||
</div>
|
||||
<div id="import_wrapper">
|
||||
<span id="format_el">ascii *mirc</span>
|
||||
<span id="import_buttons">
|
||||
<button id="import_button">import</button>
|
||||
</span>
|
||||
<div id="gallery_wrapper" /><br />
|
||||
<div id="cutoff_warning_el">character limit of 425 exceeded</div>
|
||||
<textarea id="import_textarea" cols="100" rows="30"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="cursor_input" />
|
||||
</div>
|
||||
<!-- }}} -->
|
||||
<!-- }}} -->
|
||||
<!-- {{{ SCRIPTs: assets/js/* -->
|
||||
<script src="assets/js/util.js" type="text/javascript"></script>
|
||||
<script src="assets/js/unicode.js" type="text/javascript"></script>
|
||||
<script src="assets/js/color.js" type="text/javascript"></script>
|
||||
<script src="assets/js/clipboard.js" type="text/javascript"></script>
|
||||
<script src="assets/js/lex.js" type="text/javascript"></script>
|
||||
<script src="assets/js/matrix.js" type="text/javascript"></script>
|
||||
<script src="assets/js/gfx.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/tool.js" type="text/javascript"></script>
|
||||
<script src="assets/js/gfx.js" type="text/javascript"></script>
|
||||
<!-- }}} -->
|
||||
<!-- {{{ SCRIPTs: assets/js/ui/* -->
|
||||
<script src="assets/js/ui/brush.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/canvas.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/keys.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/controls.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/paletters.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/selection.js" type="text/javascript"></script>
|
||||
<script src="assets/js/ui/transform.js" type="text/javascript"></script>
|
||||
<!-- }}} -->
|
||||
<script src="assets/js/app.js" type="text/javascript"></script>
|
||||
</body>
|
||||
<!-- }}} -->
|
||||
</html>
|
||||
|
||||
<!--
|
||||
vim:ts=2 sw=2 expandtab fenc=utf-8 foldmethod=marker nowrap tw=0
|
||||
-->
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
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 com.lalbornoz.MiRCART;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.apache.cordova.*;
|
||||
|
||||
public class MainActivity extends CordovaActivity
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// enable Cordova apps to be started in the background
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
|
||||
moveTaskToBack(true);
|
||||
}
|
||||
|
||||
// Set by <content src="index.html" /> in config.xml
|
||||
loadUrl(launchUrl);
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
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.whitelist;
|
||||
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.ConfigXmlParser;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.apache.cordova.Whitelist;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class WhitelistPlugin extends CordovaPlugin {
|
||||
private static final String LOG_TAG = "WhitelistPlugin";
|
||||
private Whitelist allowedNavigations;
|
||||
private Whitelist allowedIntents;
|
||||
private Whitelist allowedRequests;
|
||||
|
||||
// Used when instantiated via reflection by PluginManager
|
||||
public WhitelistPlugin() {
|
||||
}
|
||||
// These can be used by embedders to allow Java-configuration of whitelists.
|
||||
public WhitelistPlugin(Context context) {
|
||||
this(new Whitelist(), new Whitelist(), null);
|
||||
new CustomConfigXmlParser().parse(context);
|
||||
}
|
||||
public WhitelistPlugin(XmlPullParser xmlParser) {
|
||||
this(new Whitelist(), new Whitelist(), null);
|
||||
new CustomConfigXmlParser().parse(xmlParser);
|
||||
}
|
||||
public WhitelistPlugin(Whitelist allowedNavigations, Whitelist allowedIntents, Whitelist allowedRequests) {
|
||||
if (allowedRequests == null) {
|
||||
allowedRequests = new Whitelist();
|
||||
allowedRequests.addWhiteListEntry("file:///*", false);
|
||||
allowedRequests.addWhiteListEntry("data:*", false);
|
||||
}
|
||||
this.allowedNavigations = allowedNavigations;
|
||||
this.allowedIntents = allowedIntents;
|
||||
this.allowedRequests = allowedRequests;
|
||||
}
|
||||
@Override
|
||||
public void pluginInitialize() {
|
||||
if (allowedNavigations == null) {
|
||||
allowedNavigations = new Whitelist();
|
||||
allowedIntents = new Whitelist();
|
||||
allowedRequests = new Whitelist();
|
||||
new CustomConfigXmlParser().parse(webView.getContext());
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomConfigXmlParser extends ConfigXmlParser {
|
||||
@Override
|
||||
public void handleStartTag(XmlPullParser xml) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("content")) {
|
||||
String startPage = xml.getAttributeValue(null, "src");
|
||||
allowedNavigations.addWhiteListEntry(startPage, false);
|
||||
} else if (strNode.equals("allow-navigation")) {
|
||||
String origin = xml.getAttributeValue(null, "href");
|
||||
if ("*".equals(origin)) {
|
||||
allowedNavigations.addWhiteListEntry("http://*/*", false);
|
||||
allowedNavigations.addWhiteListEntry("https://*/*", false);
|
||||
allowedNavigations.addWhiteListEntry("data:*", false);
|
||||
} else {
|
||||
allowedNavigations.addWhiteListEntry(origin, false);
|
||||
}
|
||||
} else if (strNode.equals("allow-intent")) {
|
||||
String origin = xml.getAttributeValue(null, "href");
|
||||
allowedIntents.addWhiteListEntry(origin, false);
|
||||
} else if (strNode.equals("access")) {
|
||||
String origin = xml.getAttributeValue(null, "origin");
|
||||
String subdomains = xml.getAttributeValue(null, "subdomains");
|
||||
boolean external = (xml.getAttributeValue(null, "launch-external") != null);
|
||||
if (origin != null) {
|
||||
if (external) {
|
||||
LOG.w(LOG_TAG, "Found <access launch-external> within config.xml. Please use <allow-intent> instead.");
|
||||
allowedIntents.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||
} else {
|
||||
if ("*".equals(origin)) {
|
||||
allowedRequests.addWhiteListEntry("http://*/*", false);
|
||||
allowedRequests.addWhiteListEntry("https://*/*", false);
|
||||
} else {
|
||||
allowedRequests.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleEndTag(XmlPullParser xml) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean shouldAllowNavigation(String url) {
|
||||
if (allowedNavigations.isUrlWhiteListed(url)) {
|
||||
return true;
|
||||
}
|
||||
return null; // Default policy
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean shouldAllowRequest(String url) {
|
||||
if (Boolean.TRUE == shouldAllowNavigation(url)) {
|
||||
return true;
|
||||
}
|
||||
if (allowedRequests.isUrlWhiteListed(url)) {
|
||||
return true;
|
||||
}
|
||||
return null; // Default policy
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
if (allowedIntents.isUrlWhiteListed(url)) {
|
||||
return true;
|
||||
}
|
||||
return null; // Default policy
|
||||
}
|
||||
|
||||
public Whitelist getAllowedNavigations() {
|
||||
return allowedNavigations;
|
||||
}
|
||||
|
||||
public void setAllowedNavigations(Whitelist allowedNavigations) {
|
||||
this.allowedNavigations = allowedNavigations;
|
||||
}
|
||||
|
||||
public Whitelist getAllowedIntents() {
|
||||
return allowedIntents;
|
||||
}
|
||||
|
||||
public void setAllowedIntents(Whitelist allowedIntents) {
|
||||
this.allowedIntents = allowedIntents;
|
||||
}
|
||||
|
||||
public Whitelist getAllowedRequests() {
|
||||
return allowedRequests;
|
||||
}
|
||||
|
||||
public void setAllowedRequests(Whitelist allowedRequests) {
|
||||
this.allowedRequests = allowedRequests;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 222 KiB |
Before Width: | Height: | Size: 286 KiB |