mirror of
https://github.com/apache/cordova-android.git
synced 2026-04-04 00:02:03 +08:00
Compare commits
201 Commits
3.6.x
...
unplug-whi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198364afb9 | ||
|
|
ed78b557cd | ||
|
|
83377d366a | ||
|
|
8df2d4fcfd | ||
|
|
a0acb4ce9a | ||
|
|
fe15d34a80 | ||
|
|
23584274d2 | ||
|
|
44aa98887f | ||
|
|
cc7d352209 | ||
|
|
ce5d9a2ee8 | ||
|
|
77c51d3ae7 | ||
|
|
53dae45430 | ||
|
|
16343ffe70 | ||
|
|
b37498d5f6 | ||
|
|
7ad16e5b0c | ||
|
|
9f41906895 | ||
|
|
2af8daff1d | ||
|
|
fbeb379f1b | ||
|
|
9577735ff7 | ||
|
|
2dcd50c11b | ||
|
|
862c223e11 | ||
|
|
7f4d5aeb0e | ||
|
|
30681eb772 | ||
|
|
52e575e1e7 | ||
|
|
890e12c306 | ||
|
|
6cbf6b7875 | ||
|
|
c255a84941 | ||
|
|
ce7d6d69d9 | ||
|
|
d5538b7076 | ||
|
|
cdfa13b265 | ||
|
|
e5efc91ef4 | ||
|
|
e31c911c30 | ||
|
|
a658ea1573 | ||
|
|
6d5b88d7b9 | ||
|
|
a986e72338 | ||
|
|
162d9b6c2e | ||
|
|
f7f49d27c5 | ||
|
|
9e3ccf4b3e | ||
|
|
6b71c2f392 | ||
|
|
0d313a3964 | ||
|
|
75a0a6752a | ||
|
|
363fc8deb5 | ||
|
|
ddac192c4a | ||
|
|
69a03c2e16 | ||
|
|
2b128b85f7 | ||
|
|
b09f973231 | ||
|
|
95815a558c | ||
|
|
879da03438 | ||
|
|
d022be547b | ||
|
|
3f83fdbfc1 | ||
|
|
949152532c | ||
|
|
215adab1f9 | ||
|
|
7ce46ed60c | ||
|
|
c32bcca67b | ||
|
|
cb442364ca | ||
|
|
6bdc01290d | ||
|
|
ac34bf1e54 | ||
|
|
6fb164d200 | ||
|
|
a5d300c6ff | ||
|
|
6eb4409a72 | ||
|
|
533677df8b | ||
|
|
8f27b2ab56 | ||
|
|
25be42d385 | ||
|
|
00f6d30e08 | ||
|
|
090822eb41 | ||
|
|
d9900a725d | ||
|
|
a10106c61a | ||
|
|
5cb01f2ae9 | ||
|
|
4c1efe7ad4 | ||
|
|
4be92f285a | ||
|
|
f9b89e98c2 | ||
|
|
be01ce03d0 | ||
|
|
f221441877 | ||
|
|
18fda7ec68 | ||
|
|
f2e8c00f49 | ||
|
|
30e8b818f5 | ||
|
|
525ce0e0ad | ||
|
|
3cd567dc95 | ||
|
|
2f7ffa3636 | ||
|
|
d99386ef1e | ||
|
|
9ae3d2c074 | ||
|
|
dd5a337a49 | ||
|
|
51e634ccb4 | ||
|
|
31b1a821ca | ||
|
|
bf13fd48ce | ||
|
|
3b99760a42 | ||
|
|
0e78dc35d8 | ||
|
|
c8bbdb23de | ||
|
|
7ee8117186 | ||
|
|
8237c41143 | ||
|
|
d52ca93ba6 | ||
|
|
8354651059 | ||
|
|
81cc3c260f | ||
|
|
4dc32e194b | ||
|
|
5a82dd5110 | ||
|
|
f20708a5e7 | ||
|
|
91cf78f183 | ||
|
|
0b6b068097 | ||
|
|
623b2306ca | ||
|
|
233e513860 | ||
|
|
7caa96abcd | ||
|
|
b2776269cf | ||
|
|
4c1942e3fe | ||
|
|
a7ccb9243d | ||
|
|
f9b8f9a45f | ||
|
|
5054b714e2 | ||
|
|
05868b541b | ||
|
|
a40424e75c | ||
|
|
a99c8219bd | ||
|
|
a03fdaba39 | ||
|
|
6f301576eb | ||
|
|
e2b3f76a10 | ||
|
|
b277202838 | ||
|
|
a4f6d9f6e7 | ||
|
|
1d4aa44d3d | ||
|
|
b52fcb8aa9 | ||
|
|
f0da63a8ff | ||
|
|
f38c460588 | ||
|
|
9b9c59766f | ||
|
|
fc2a202afa | ||
|
|
4b4b71ff32 | ||
|
|
9358838dab | ||
|
|
a4d9f702e4 | ||
|
|
efcedabee0 | ||
|
|
25a7b66296 | ||
|
|
ac194cd34f | ||
|
|
eca05e6bad | ||
|
|
84bf20152b | ||
|
|
200e9f1a8e | ||
|
|
7dc09b4019 | ||
|
|
dbb196a17e | ||
|
|
05a95c699f | ||
|
|
9c5e340fb8 | ||
|
|
67006add53 | ||
|
|
1571b26a65 | ||
|
|
bdf2f22f81 | ||
|
|
a8330773ca | ||
|
|
4ca2305693 | ||
|
|
428e1bc14d | ||
|
|
d66bb84924 | ||
|
|
4ce5123a12 | ||
|
|
96a1192474 | ||
|
|
c052f40ef8 | ||
|
|
98246c0e35 | ||
|
|
8ac067da89 | ||
|
|
0ffb5d253a | ||
|
|
3a9898a6a6 | ||
|
|
693ec14df5 | ||
|
|
fa189b3234 | ||
|
|
3b27cd093b | ||
|
|
6abb9da88a | ||
|
|
d5e8807756 | ||
|
|
7e9fdb3555 | ||
|
|
b42faea2eb | ||
|
|
635a6279a9 | ||
|
|
404d3e0959 | ||
|
|
f77b20bbca | ||
|
|
1d0a1664e6 | ||
|
|
410afbf9a1 | ||
|
|
aaddfa6f3a | ||
|
|
2d9a16e857 | ||
|
|
1dcba51092 | ||
|
|
7c63b30de1 | ||
|
|
c0eae1ad52 | ||
|
|
c012b98223 | ||
|
|
559493babd | ||
|
|
990ab2c7ef | ||
|
|
437003de29 | ||
|
|
22b1959333 | ||
|
|
97008305ff | ||
|
|
1a17083e8c | ||
|
|
b6664cc859 | ||
|
|
e595c313a1 | ||
|
|
955da2e360 | ||
|
|
04b3fc0268 | ||
|
|
105ccc81a5 | ||
|
|
3571307df5 | ||
|
|
df05f3a3c0 | ||
|
|
8e31ef7be6 | ||
|
|
f4555f7c96 | ||
|
|
8408da55ea | ||
|
|
4a67dd2e28 | ||
|
|
bd806a34d8 | ||
|
|
2f7e833a79 | ||
|
|
c17503ab78 | ||
|
|
19f76d34db | ||
|
|
25c8b2fabb | ||
|
|
bfd8bf9ca4 | ||
|
|
7a5405d2ab | ||
|
|
b9a24f00ad | ||
|
|
dbfc292353 | ||
|
|
a09255b2ff | ||
|
|
9d1c72cc07 | ||
|
|
09ac30ef2e | ||
|
|
79e313a0c0 | ||
|
|
9f4c75d1c2 | ||
|
|
b37492644c | ||
|
|
04a792a8c2 | ||
|
|
35ec24c3f0 | ||
|
|
61b23677d1 | ||
|
|
90037dc6cd |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -37,3 +37,6 @@ Desktop.ini
|
|||||||
# IntelliJ IDEA files
|
# IntelliJ IDEA files
|
||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
|
npm-debug.log
|
||||||
|
/node_modules
|
||||||
|
/framework/build
|
||||||
|
|||||||
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
language: android
|
||||||
|
install: npm install
|
||||||
|
script:
|
||||||
|
- npm test
|
||||||
|
- npm run test-build
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
* CB-7463: Adding licences. I don't know what the gradle syntax is for comments, that still needs to be done.
|
* CB-7463: Adding licences. I don't know what the gradle syntax is for comments, that still needs to be done.
|
||||||
* CB-7463: Looked at the Apache BigTop git, gradle uses C-style comments
|
* CB-7463: Looked at the Apache BigTop git, gradle uses C-style comments
|
||||||
* CB-7460: Fixing bug with KitKat where the background colour would override the CSS colours on the application
|
* CB-7460: Fixing bug with KitKat where the background colour would override the CSS colours on the application
|
||||||
* CB-7674 Preference activation no longer occurs in CordovaActivity.onCreate()
|
|
||||||
|
|
||||||
### 3.6.0 (Sept 2014) ###
|
### 3.6.0 (Sept 2014) ###
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ var isWindows = process.platform == 'win32';
|
|||||||
|
|
||||||
function forgivingWhichSync(cmd) {
|
function forgivingWhichSync(cmd) {
|
||||||
try {
|
try {
|
||||||
return which.sync(cmd);
|
return fs.realpathSync(which.sync(cmd));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -48,20 +48,21 @@ function tryCommand(cmd, errMsg) {
|
|||||||
|
|
||||||
// Get valid target from framework/project.properties
|
// Get valid target from framework/project.properties
|
||||||
module.exports.get_target = function() {
|
module.exports.get_target = function() {
|
||||||
if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
function extractFromFile(filePath) {
|
||||||
var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties'));
|
var target = shelljs.grep(/\btarget=/, filePath);
|
||||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
if (!target) {
|
||||||
} else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
throw new Error('Could not find android target within: ' + filePath);
|
||||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
|
||||||
// this is called on the project itself, and can support Google APIs AND Vanilla Android
|
|
||||||
var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
|
|
||||||
shelljs.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
|
|
||||||
if(target == "" || !target) {
|
|
||||||
// Try Google Glass APIs
|
|
||||||
target = shelljs.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties'));
|
|
||||||
}
|
}
|
||||||
return target.split('=')[1].replace('\n', '').replace('\r', '');
|
return target.split('=')[1].trim();
|
||||||
}
|
}
|
||||||
|
if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
|
||||||
|
return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||||
|
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||||
|
return extractFromFile(path.join(ROOT, 'project.properties'));
|
||||||
|
}
|
||||||
|
throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise. Called only by build and clean commands.
|
// Returns a promise. Called only by build and clean commands.
|
||||||
@@ -101,7 +102,7 @@ module.exports.check_java = function() {
|
|||||||
} else {
|
} else {
|
||||||
// See if we can derive it from javac's location.
|
// See if we can derive it from javac's location.
|
||||||
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
|
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
|
||||||
var maybeJavaHome = path.dirname(path.dirname(fs.realpathSync(javacPath)));
|
var maybeJavaHome = path.dirname(path.dirname(javacPath));
|
||||||
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
|
||||||
process.env['JAVA_HOME'] = maybeJavaHome;
|
process.env['JAVA_HOME'] = maybeJavaHome;
|
||||||
} else {
|
} else {
|
||||||
@@ -110,10 +111,13 @@ module.exports.check_java = function() {
|
|||||||
}
|
}
|
||||||
} else if (isWindows) {
|
} else if (isWindows) {
|
||||||
// Try to auto-detect java in the default install paths.
|
// Try to auto-detect java in the default install paths.
|
||||||
|
var oldSilent = shelljs.config.silent;
|
||||||
|
shelljs.config.silent = true;
|
||||||
var firstJdkDir =
|
var firstJdkDir =
|
||||||
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
|
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
|
||||||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
|
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
|
||||||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
|
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
|
||||||
|
shelljs.config.silent = oldSilent;
|
||||||
if (firstJdkDir) {
|
if (firstJdkDir) {
|
||||||
// shelljs always uses / in paths.
|
// shelljs always uses / in paths.
|
||||||
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
|
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
|
||||||
@@ -126,13 +130,15 @@ module.exports.check_java = function() {
|
|||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
var msg =
|
var msg =
|
||||||
'Failed to run "java -version", make sure your java environment is set up\n' +
|
'Failed to run "java -version", make sure that you have a JDK installed.\n' +
|
||||||
'including JDK and JRE.\n' +
|
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
|
||||||
'Your JAVA_HOME variable is: ' + process.env['JAVA_HOME'];
|
if (process.env['JAVA_HOME']) {
|
||||||
|
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
|
||||||
|
}
|
||||||
return tryCommand('java -version', msg)
|
return tryCommand('java -version', msg)
|
||||||
}).then(function() {
|
.then(function() {
|
||||||
msg = 'Failed to run "javac -version", make sure you have a Java JDK (not just a JRE) installed.';
|
return tryCommand('javac -version', msg);
|
||||||
return tryCommand('javac -version', msg)
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,14 +148,46 @@ module.exports.check_android = function() {
|
|||||||
var androidCmdPath = forgivingWhichSync('android');
|
var androidCmdPath = forgivingWhichSync('android');
|
||||||
var adbInPath = !!forgivingWhichSync('adb');
|
var adbInPath = !!forgivingWhichSync('adb');
|
||||||
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
|
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
|
||||||
|
function maybeSetAndroidHome(value) {
|
||||||
|
if (!hasAndroidHome && fs.existsSync(value)) {
|
||||||
|
hasAndroidHome = true;
|
||||||
|
process.env['ANDROID_HOME'] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAndroidHome && !androidCmdPath) {
|
||||||
|
if (isWindows) {
|
||||||
|
// Android Studio installer.
|
||||||
|
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
|
||||||
|
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
|
||||||
|
// Stand-alone installer.
|
||||||
|
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
|
||||||
|
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
|
||||||
|
} else if (process.platform == 'darwin') {
|
||||||
|
maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
|
||||||
|
// Stand-alone zip file that user might think to put under /Applications
|
||||||
|
maybeSetAndroidHome('/Applications/android-sdk-macosx');
|
||||||
|
maybeSetAndroidHome('/Applications/android-sdk');
|
||||||
|
}
|
||||||
|
if (process.env['HOME']) {
|
||||||
|
// or their HOME directory.
|
||||||
|
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
|
||||||
|
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (hasAndroidHome && !androidCmdPath) {
|
if (hasAndroidHome && !androidCmdPath) {
|
||||||
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
|
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
|
||||||
}
|
}
|
||||||
if (androidCmdPath && !hasAndroidHome) {
|
if (androidCmdPath && !hasAndroidHome) {
|
||||||
var parentDir = path.dirname(androidCmdPath);
|
var parentDir = path.dirname(androidCmdPath);
|
||||||
|
var grandParentDir = path.dirname(parentDir);
|
||||||
if (path.basename(parentDir) == 'tools') {
|
if (path.basename(parentDir) == 'tools') {
|
||||||
process.env['ANDROID_HOME'] = path.dirname(parentDir);
|
process.env['ANDROID_HOME'] = path.dirname(parentDir);
|
||||||
hasAndroidHome = true;
|
hasAndroidHome = true;
|
||||||
|
} else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
|
||||||
|
process.env['ANDROID_HOME'] = grandParentDir;
|
||||||
|
hasAndroidHome = true;
|
||||||
|
} else {
|
||||||
|
throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasAndroidHome && !adbInPath) {
|
if (hasAndroidHome && !adbInPath) {
|
||||||
@@ -166,13 +204,27 @@ module.exports.check_android = function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.getAbsoluteAndroidCmd = function() {
|
||||||
|
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.check_android_target = function(valid_target) {
|
module.exports.check_android_target = function(valid_target) {
|
||||||
var msg = 'Failed to run "android". Make sure you have the latest Android SDK installed, and that the "android" command (inside the tools/ folder) is added to your PATH.';
|
// valid_target can look like:
|
||||||
return tryCommand('android list targets', msg)
|
// android-19
|
||||||
|
// android-L
|
||||||
|
// Google Inc.:Google APIs:20
|
||||||
|
// Google Inc.:Glass Development Kit Preview:20
|
||||||
|
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
|
||||||
|
return tryCommand('android list targets --compact', msg)
|
||||||
.then(function(output) {
|
.then(function(output) {
|
||||||
if (!output.match(valid_target)) {
|
if (output.split('\n').indexOf(valid_target) == -1) {
|
||||||
throw new Error('Please install Android target "' + valid_target + '".\n' +
|
var androidCmd = module.exports.getAbsoluteAndroidCmd();
|
||||||
'Hint: Run "android" from your command-line to open the SDK manager.');
|
throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
|
||||||
|
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
|
||||||
|
'You will require:\n' +
|
||||||
|
'1. "SDK Platform" for ' + valid_target + '\n' +
|
||||||
|
'2. "Android SDK Platform-tools (latest)\n' +
|
||||||
|
'3. "Android SDK Build-tools" (latest)');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -119,10 +119,9 @@ function writeProjectProperties(projectPath, target_api, shared) {
|
|||||||
|
|
||||||
function copyBuildRules(projectPath) {
|
function copyBuildRules(projectPath) {
|
||||||
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
||||||
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
|
|
||||||
|
|
||||||
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
|
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
|
||||||
shell.cp('-f', path.join(srcDir, 'settings.gradle'), projectPath);
|
shell.cp('-f', path.join(srcDir, 'cordova.gradle'), projectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyScripts(projectPath) {
|
function copyScripts(projectPath) {
|
||||||
@@ -214,7 +213,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
var activity_dir = path.join(project_path, 'src', package_as_path);
|
var activity_dir = path.join(project_path, 'src', package_as_path);
|
||||||
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
|
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
|
||||||
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
|
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
|
||||||
var safe_activity_name = "CordovaApp";
|
var safe_activity_name = 'MainActivity';
|
||||||
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
|
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
|
||||||
var target_api = check_reqs.get_target();
|
var target_api = check_reqs.get_target();
|
||||||
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
|
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
|
||||||
@@ -228,10 +227,6 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
return validatePackageName(package_name)
|
return validatePackageName(package_name)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
validateProjectName(project_name);
|
validateProjectName(project_name);
|
||||||
})
|
|
||||||
// Check that requirements are met and proper targets are installed
|
|
||||||
.then(function() {
|
|
||||||
return check_reqs.run();
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
// Log the given values for the project
|
// Log the given values for the project
|
||||||
console.log('Creating Cordova project for the Android platform:');
|
console.log('Creating Cordova project for the Android platform:');
|
||||||
@@ -282,7 +277,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
copyBuildRules(project_path);
|
copyBuildRules(project_path);
|
||||||
});
|
});
|
||||||
// Link it to local android install.
|
// Link it to local android install.
|
||||||
writeProjectProperties(project_path, target_api);
|
writeProjectProperties(project_path, target_api, use_shared_project);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
console.log('Project successfully created.');
|
console.log('Project successfully created.');
|
||||||
});
|
});
|
||||||
@@ -307,8 +302,7 @@ function extractProjectNameFromManifest(projectPath) {
|
|||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
exports.updateProject = function(projectPath, shared) {
|
exports.updateProject = function(projectPath, shared) {
|
||||||
var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||||
// Check that requirements are met and proper targets are installed
|
return Q()
|
||||||
return check_reqs.run()
|
|
||||||
.then(function() {
|
.then(function() {
|
||||||
var projectName = extractProjectNameFromManifest(projectPath);
|
var projectName = extractProjectNameFromManifest(projectPath);
|
||||||
var target_api = check_reqs.get_target();
|
var target_api = check_reqs.get_target();
|
||||||
|
|||||||
93
bin/templates/cordova/lib/build.js
vendored
93
bin/templates/cordova/lib/build.js
vendored
@@ -20,13 +20,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var shell = require('shelljs'),
|
var shell = require('shelljs'),
|
||||||
exec = require('./exec'),
|
|
||||||
spawn = require('./spawn'),
|
spawn = require('./spawn'),
|
||||||
Q = require('q'),
|
Q = require('q'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
ROOT = path.join(__dirname, '..', '..');
|
ROOT = path.join(__dirname, '..', '..');
|
||||||
var check_reqs = require('./check_reqs');
|
var check_reqs = require('./check_reqs');
|
||||||
|
var exec = require('./exec');
|
||||||
|
|
||||||
var LOCAL_PROPERTIES_TEMPLATE =
|
var LOCAL_PROPERTIES_TEMPLATE =
|
||||||
'# This file is automatically generated.\n' +
|
'# This file is automatically generated.\n' +
|
||||||
@@ -53,7 +53,7 @@ function sortFilesByDate(files) {
|
|||||||
}).map(function(p) { return p.p; });
|
}).map(function(p) { return p.p; });
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOutputApksHelper(dir, build_type) {
|
function findOutputApksHelper(dir, build_type, arch) {
|
||||||
var ret = findApks(dir).filter(function(candidate) {
|
var ret = findApks(dir).filter(function(candidate) {
|
||||||
// Need to choose between release and debug .apk.
|
// Need to choose between release and debug .apk.
|
||||||
if (build_type === 'debug') {
|
if (build_type === 'debug') {
|
||||||
@@ -69,9 +69,15 @@ function findOutputApksHelper(dir, build_type) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
|
var archSpecific = !!/-x86|-arm/.exec(ret[0]);
|
||||||
return ret.filter(function(p) {
|
ret = ret.filter(function(p) {
|
||||||
return !!/-x86|-arm/.exec(p) == archSpecific;
|
return !!/-x86|-arm/.exec(p) == archSpecific;
|
||||||
});
|
});
|
||||||
|
if (arch) {
|
||||||
|
ret = ret.filter(function(p) {
|
||||||
|
return p.indexOf('-' + arch) != -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasCustomRules() {
|
function hasCustomRules() {
|
||||||
@@ -163,11 +169,11 @@ var builders = {
|
|||||||
|
|
||||||
findOutputApks: function(build_type) {
|
findOutputApks: function(build_type) {
|
||||||
var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
|
var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
|
||||||
return findOutputApksHelper(binDir, build_type);
|
return findOutputApksHelper(binDir, build_type, null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
gradle: {
|
gradle: {
|
||||||
getArgs: function(cmd) {
|
getArgs: function(cmd, arch) {
|
||||||
var lintSteps;
|
var lintSteps;
|
||||||
if (process.env['BUILD_MULTIPLE_APKS']) {
|
if (process.env['BUILD_MULTIPLE_APKS']) {
|
||||||
lintSteps = [
|
lintSteps = [
|
||||||
@@ -187,6 +193,19 @@ var builders = {
|
|||||||
'copyDebugLint'
|
'copyDebugLint'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (arch == 'arm' && cmd == 'debug') {
|
||||||
|
cmd = 'assembleArmv7Debug';
|
||||||
|
} else if (arch == 'arm' && cmd == 'release') {
|
||||||
|
cmd = 'assembleArmv7Release';
|
||||||
|
} else if (arch == 'x86' && cmd == 'debug') {
|
||||||
|
cmd = 'assembleX86Debug';
|
||||||
|
} else if (arch == 'x86' && cmd == 'release') {
|
||||||
|
cmd = 'assembleX86Release';
|
||||||
|
} else if (cmd == 'debug') {
|
||||||
|
cmd = 'assembleDebug';
|
||||||
|
} else if (cmd == 'release') {
|
||||||
|
cmd = 'assembleRelease';
|
||||||
|
}
|
||||||
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
|
var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
|
||||||
// 10 seconds -> 6 seconds
|
// 10 seconds -> 6 seconds
|
||||||
args.push('-Dorg.gradle.daemon=true');
|
args.push('-Dorg.gradle.daemon=true');
|
||||||
@@ -217,6 +236,36 @@ var builders = {
|
|||||||
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
|
shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
|
||||||
shell.mkdir('-p', path.join(projectPath, 'gradle'));
|
shell.mkdir('-p', path.join(projectPath, 'gradle'));
|
||||||
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
|
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
|
||||||
|
|
||||||
|
// If the gradle distribution URL is set, make sure it points to version 1.12.
|
||||||
|
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
|
||||||
|
var distributionUrlRegex = '/^distributionUrl=.*$/';
|
||||||
|
var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-1.12-all.zip';
|
||||||
|
var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
|
||||||
|
shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
|
||||||
|
|
||||||
|
// Update the version of build.gradle in each dependent library.
|
||||||
|
var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
|
||||||
|
var subProjects = extractSubProjectPaths();
|
||||||
|
for (var i = 0; i < subProjects.length; ++i) {
|
||||||
|
shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var subProjectsAsGradlePaths = subProjects.map(function(p) { return ':' + p.replace(/[/\\]/g, ':') });
|
||||||
|
// Write the settings.gradle file.
|
||||||
|
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
|
||||||
|
'// GENERATED FILE - DO NOT EDIT\n' +
|
||||||
|
'include ":"\n' +
|
||||||
|
'include "' + subProjectsAsGradlePaths.join('"\ninclude "') + '"\n');
|
||||||
|
// Update dependencies within build.gradle.
|
||||||
|
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
|
||||||
|
var depsList = '';
|
||||||
|
subProjectsAsGradlePaths.forEach(function(p) {
|
||||||
|
depsList += ' debugCompile project(path: "' + p + '", configuration: "debug")\n';
|
||||||
|
depsList += ' releaseCompile project(path: "' + p + '", configuration: "release")\n';
|
||||||
|
});
|
||||||
|
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
|
||||||
|
fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -224,10 +273,10 @@ var builders = {
|
|||||||
* Builds the project with gradle.
|
* Builds the project with gradle.
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
build: function(build_type) {
|
build: function(build_type, arch) {
|
||||||
var builder = this;
|
var builder = this;
|
||||||
var wrapper = path.join(ROOT, 'gradlew');
|
var wrapper = path.join(ROOT, 'gradlew');
|
||||||
var args = builder.getArgs('build');
|
var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch);
|
||||||
return Q().then(function() {
|
return Q().then(function() {
|
||||||
return spawn(wrapper, args);
|
return spawn(wrapper, args);
|
||||||
});
|
});
|
||||||
@@ -242,9 +291,9 @@ var builders = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
findOutputApks: function(build_type) {
|
findOutputApks: function(build_type, arch) {
|
||||||
var binDir = path.join(ROOT, 'build', 'apk');
|
var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
|
||||||
return findOutputApksHelper(binDir, build_type);
|
return findOutputApksHelper(binDir, build_type, arch);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -259,19 +308,20 @@ var builders = {
|
|||||||
clean: function() {
|
clean: function() {
|
||||||
return Q();
|
return Q();
|
||||||
},
|
},
|
||||||
findOutputApks: function(build_type) {
|
findOutputApks: function(build_type, arch) {
|
||||||
return sortFilesByDate(builders.ant.findOutputApks(build_type).concat(builders.gradle.findOutputApks(build_type)));
|
return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseOpts(options) {
|
function parseOpts(options, resolvedTarget) {
|
||||||
// Backwards-compatibility: Allow a single string argument
|
// Backwards-compatibility: Allow a single string argument
|
||||||
if (typeof options == "string") options = [options];
|
if (typeof options == "string") options = [options];
|
||||||
|
|
||||||
var ret = {
|
var ret = {
|
||||||
buildType: 'debug',
|
buildType: 'debug',
|
||||||
buildMethod: process.env['ANDROID_BUILD'] || 'ant'
|
buildMethod: process.env['ANDROID_BUILD'] || 'ant',
|
||||||
|
arch: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate through command line options
|
// Iterate through command line options
|
||||||
@@ -297,6 +347,12 @@ function parseOpts(options) {
|
|||||||
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
|
return Q.reject('Build option \'' + options[i] + '\' not recognized.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var multiApk = ret.buildMethod == 'gradle' && process.env['BUILD_MULTIPLE_APKS'];
|
||||||
|
if (multiApk && !/0|false|no/i.exec(multiApk)) {
|
||||||
|
ret.arch = resolvedTarget && resolvedTarget.arch;
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,15 +375,14 @@ module.exports.runClean = function(options) {
|
|||||||
* Builds the project with the specifed options
|
* Builds the project with the specifed options
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
module.exports.run = function(options) {
|
module.exports.run = function(options, optResolvedTarget) {
|
||||||
var opts = parseOpts(options);
|
var opts = parseOpts(options, optResolvedTarget);
|
||||||
|
|
||||||
var builder = builders[opts.buildMethod];
|
var builder = builders[opts.buildMethod];
|
||||||
return builder.prepEnv()
|
return builder.prepEnv()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return builder.build(opts.buildType);
|
return builder.build(opts.buildType, opts.arch);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
var apkPaths = builder.findOutputApks(opts.buildType);
|
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
|
||||||
console.log('Built the following apk(s):');
|
console.log('Built the following apk(s):');
|
||||||
console.log(' ' + apkPaths.join('\n '));
|
console.log(' ' + apkPaths.join('\n '));
|
||||||
return {
|
return {
|
||||||
|
|||||||
80
bin/templates/cordova/lib/device.js
vendored
80
bin/templates/cordova/lib/device.js
vendored
@@ -43,48 +43,60 @@ module.exports.list = function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.resolveTarget = function(target) {
|
||||||
|
return this.list()
|
||||||
|
.then(function(device_list) {
|
||||||
|
if (!device_list || !device_list.length) {
|
||||||
|
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
|
||||||
|
}
|
||||||
|
// default device
|
||||||
|
target = target || device_list[0];
|
||||||
|
|
||||||
|
if (device_list.indexOf(target) < 0) {
|
||||||
|
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.detectArchitecture(target)
|
||||||
|
.then(function(arch) {
|
||||||
|
return { target: target, arch: arch, isEmulator: false };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Installs a previously built application on the device
|
* Installs a previously built application on the device
|
||||||
* and launches it.
|
* and launches it.
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
module.exports.install = function(target, buildResults) {
|
module.exports.install = function(target, buildResults) {
|
||||||
var launchName;
|
return Q().then(function() {
|
||||||
return this.list()
|
if (target && typeof target == 'object') {
|
||||||
.then(function(device_list) {
|
return target;
|
||||||
if (!device_list || !device_list.length)
|
}
|
||||||
return Q.reject('ERROR: Failed to deploy to device, no devices found.');
|
return module.exports.resolveTarget(target);
|
||||||
|
}).then(function(resolvedTarget) {
|
||||||
|
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
|
||||||
|
var launchName = appinfo.getActivityName();
|
||||||
|
console.log('Using apk: ' + apk_path);
|
||||||
|
console.log('Installing app on device...');
|
||||||
|
var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"';
|
||||||
|
return exec(cmd)
|
||||||
|
.then(function(output) {
|
||||||
|
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
||||||
|
|
||||||
// default device
|
//unlock screen
|
||||||
target = target || device_list[0];
|
var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82';
|
||||||
|
|
||||||
if (device_list.indexOf(target) < 0)
|
|
||||||
return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
|
|
||||||
|
|
||||||
return build.detectArchitecture(target)
|
|
||||||
.then(function(arch) {
|
|
||||||
var apk_path = build.findBestApkForArchitecture(buildResults, arch);
|
|
||||||
launchName = appinfo.getActivityName();
|
|
||||||
console.log('Using apk: ' + apk_path);
|
|
||||||
console.log('Installing app on device...');
|
|
||||||
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
|
|
||||||
return exec(cmd);
|
return exec(cmd);
|
||||||
|
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
|
||||||
|
.then(function() {
|
||||||
|
// launch the application
|
||||||
|
console.log('Launching application...');
|
||||||
|
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||||
|
return exec(cmd);
|
||||||
|
}).then(function() {
|
||||||
|
console.log('LAUNCH SUCCESS');
|
||||||
|
}, function(err) {
|
||||||
|
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
||||||
});
|
});
|
||||||
}).then(function(output) {
|
|
||||||
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
|
||||||
|
|
||||||
//unlock screen
|
|
||||||
var cmd = 'adb -s ' + target + ' shell input keyevent 82';
|
|
||||||
return exec(cmd);
|
|
||||||
}, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
|
|
||||||
.then(function() {
|
|
||||||
// launch the application
|
|
||||||
console.log('Launching application...');
|
|
||||||
var cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
|
||||||
return exec(cmd);
|
|
||||||
}).then(function() {
|
|
||||||
console.log('LAUNCH SUCCESS');
|
|
||||||
}, function(err) {
|
|
||||||
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
93
bin/templates/cordova/lib/emulator.js
vendored
93
bin/templates/cordova/lib/emulator.js
vendored
@@ -28,6 +28,7 @@ var shell = require('shelljs'),
|
|||||||
ROOT = path.join(__dirname, '..', '..'),
|
ROOT = path.join(__dirname, '..', '..'),
|
||||||
child_process = require('child_process'),
|
child_process = require('child_process'),
|
||||||
new_emulator = 'cordova_emulator';
|
new_emulator = 'cordova_emulator';
|
||||||
|
var check_reqs = require('./check_reqs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Promise for a list of emulator images in the form of objects
|
* Returns a Promise for a list of emulator images in the form of objects
|
||||||
@@ -84,7 +85,7 @@ module.exports.list_images = function() {
|
|||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
module.exports.best_image = function() {
|
module.exports.best_image = function() {
|
||||||
var project_target = this.get_target().replace('android-', '');
|
var project_target = check_reqs.get_target().replace('android-', '');
|
||||||
return this.list_images()
|
return this.list_images()
|
||||||
.then(function(images) {
|
.then(function(images) {
|
||||||
var closest = 9999;
|
var closest = 9999;
|
||||||
@@ -120,11 +121,6 @@ module.exports.list_started = function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.get_target = function() {
|
|
||||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
|
|
||||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
module.exports.list_targets = function() {
|
module.exports.list_targets = function() {
|
||||||
return exec('android list targets')
|
return exec('android list targets')
|
||||||
@@ -156,7 +152,7 @@ module.exports.start = function(emulator_ID) {
|
|||||||
.then(function(list) {
|
.then(function(list) {
|
||||||
started_emulators = list;
|
started_emulators = list;
|
||||||
num_started = started_emulators.length;
|
num_started = started_emulators.length;
|
||||||
if (typeof emulator_ID === 'undefined') {
|
if (!emulator_ID) {
|
||||||
return self.list_images()
|
return self.list_images()
|
||||||
.then(function(emulator_list) {
|
.then(function(emulator_list) {
|
||||||
if (emulator_list.length > 0) {
|
if (emulator_list.length > 0) {
|
||||||
@@ -167,11 +163,11 @@ module.exports.start = function(emulator_ID) {
|
|||||||
return emulator_ID;
|
return emulator_ID;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
|
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
|
||||||
' avd follow the instructions provided here:\n' +
|
return Q.reject('ERROR : No emulator images (avds) found.\n' +
|
||||||
' http://developer.android.com/tools/devices/index.html\n' +
|
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
|
||||||
' Or run \'android create avd --name <name> --target <targetID>\'\n' +
|
'2. Create an AVD by running: ' + androidCmd + ' avd\n' +
|
||||||
' in on the command line.');
|
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -277,14 +273,7 @@ module.exports.create_image = function(name, target) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
module.exports.resolveTarget = function(target) {
|
||||||
* Installs a previously built application on the emulator and launches it.
|
|
||||||
* If no target is specified, then it picks one.
|
|
||||||
* If no started emulators are found, error out.
|
|
||||||
* Returns a promise.
|
|
||||||
*/
|
|
||||||
module.exports.install = function(target, buildResults) {
|
|
||||||
var self = this;
|
|
||||||
return this.list_started()
|
return this.list_started()
|
||||||
.then(function(emulator_list) {
|
.then(function(emulator_list) {
|
||||||
if (emulator_list.length < 1) {
|
if (emulator_list.length < 1) {
|
||||||
@@ -299,30 +288,48 @@ module.exports.install = function(target, buildResults) {
|
|||||||
|
|
||||||
return build.detectArchitecture(target)
|
return build.detectArchitecture(target)
|
||||||
.then(function(arch) {
|
.then(function(arch) {
|
||||||
var apk_path = build.findBestApkForArchitecture(buildResults, arch);
|
return {target:target, arch:arch, isEmulator:true};
|
||||||
console.log('Installing app on emulator...');
|
|
||||||
console.log('Using apk: ' + apk_path);
|
|
||||||
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
|
|
||||||
});
|
});
|
||||||
}).then(function(output) {
|
});
|
||||||
if (output.match(/Failure/)) {
|
};
|
||||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
|
||||||
|
/*
|
||||||
|
* Installs a previously built application on the emulator and launches it.
|
||||||
|
* If no target is specified, then it picks one.
|
||||||
|
* If no started emulators are found, error out.
|
||||||
|
* Returns a promise.
|
||||||
|
*/
|
||||||
|
module.exports.install = function(target, buildResults) {
|
||||||
|
return Q().then(function() {
|
||||||
|
if (target && typeof target == 'object') {
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
return Q();
|
return module.exports.resolveTarget(target);
|
||||||
}, function(err) {
|
}).then(function(resolvedTarget) {
|
||||||
return Q.reject('Failed to install apk to emulator: ' + err);
|
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
|
||||||
}).then(function() {
|
console.log('Installing app on emulator...');
|
||||||
//unlock screen
|
console.log('Using apk: ' + apk_path);
|
||||||
return exec('adb -s ' + target + ' shell input keyevent 82');
|
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"')
|
||||||
}).then(function() {
|
.then(function(output) {
|
||||||
// launch the application
|
if (output.match(/Failure/)) {
|
||||||
console.log('Launching application...');
|
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||||
var launchName = appinfo.getActivityName();
|
}
|
||||||
cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
return Q();
|
||||||
return exec(cmd);
|
}, function(err) {
|
||||||
}).then(function(output) {
|
return Q.reject('Failed to install apk to emulator: ' + err);
|
||||||
console.log('LAUNCH SUCCESS');
|
}).then(function() {
|
||||||
}, function(err) {
|
//unlock screen
|
||||||
return Q.reject('Failed to launch app on emulator: ' + err);
|
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82');
|
||||||
|
}).then(function() {
|
||||||
|
// launch the application
|
||||||
|
console.log('Launching application...');
|
||||||
|
var launchName = appinfo.getActivityName();
|
||||||
|
cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||||
|
return exec(cmd);
|
||||||
|
}).then(function(output) {
|
||||||
|
console.log('LAUNCH SUCCESS');
|
||||||
|
}, function(err) {
|
||||||
|
return Q.reject('Failed to launch app on emulator: ' + err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
63
bin/templates/cordova/lib/plugin-build.gradle
Normal file
63
bin/templates/cordova/lib/plugin-build.gradle
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GENERATED FILE! DO NOT EDIT!
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:0.12.+'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'android-library'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion cordova.cordovaSdkVersion
|
||||||
|
buildToolsVersion cordova.cordovaBuildToolsVersion
|
||||||
|
publishNonDefault true
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
aidl.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
res.srcDirs = ['res']
|
||||||
|
assets.srcDirs = ['assets']
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file('build-extras.gradle').exists()) {
|
||||||
|
apply from: 'build-extras.gradle'
|
||||||
|
}
|
||||||
109
bin/templates/cordova/lib/run.js
vendored
109
bin/templates/cordova/lib/run.js
vendored
@@ -55,73 +55,66 @@ var path = require('path'),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return build.run(buildFlags).then(function(buildResults) {
|
return Q()
|
||||||
if (install_target == '--device') {
|
.then(function() {
|
||||||
return device.install(null, buildResults);
|
if (!install_target) {
|
||||||
} else if (install_target == '--emulator') {
|
|
||||||
return emulator.list_started().then(function(started) {
|
|
||||||
var p = started && started.length > 0 ? Q() : emulator.start();
|
|
||||||
return p.then(function() { return emulator.install(null, buildResults); });
|
|
||||||
});
|
|
||||||
} else if (install_target) {
|
|
||||||
var devices, started_emulators, avds;
|
|
||||||
return device.list()
|
|
||||||
.then(function(res) {
|
|
||||||
devices = res;
|
|
||||||
return emulator.list_started();
|
|
||||||
}).then(function(res) {
|
|
||||||
started_emulators = res;
|
|
||||||
return emulator.list_images();
|
|
||||||
}).then(function(res) {
|
|
||||||
avds = res;
|
|
||||||
if (devices.indexOf(install_target) > -1) {
|
|
||||||
return device.install(install_target, buildResults);
|
|
||||||
} else if (started_emulators.indexOf(install_target) > -1) {
|
|
||||||
return emulator.install(install_target, buildResults);
|
|
||||||
} else {
|
|
||||||
// if target emulator isn't started, then start it.
|
|
||||||
var emulator_ID;
|
|
||||||
for(avd in avds) {
|
|
||||||
if(avds[avd].name == install_target) {
|
|
||||||
return emulator.start(install_target)
|
|
||||||
.then(function() { emulator.install(emulator_ID, buildResults); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// no target given, deploy to device if available, otherwise use the emulator.
|
// no target given, deploy to device if available, otherwise use the emulator.
|
||||||
return device.list()
|
return device.list()
|
||||||
.then(function(device_list) {
|
.then(function(device_list) {
|
||||||
if (device_list.length > 0) {
|
if (device_list.length > 0) {
|
||||||
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
|
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
|
||||||
return device.install(device_list[0], buildResults);
|
install_target = device_list[0];
|
||||||
} else {
|
} else {
|
||||||
return emulator.list_started()
|
console.log('WARNING : No target specified, deploying to emulator');
|
||||||
.then(function(emulator_list) {
|
install_target = '--emulator';
|
||||||
if (emulator_list.length > 0) {
|
|
||||||
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_list[0] + '\'.');
|
|
||||||
return emulator.install(emulator_list[0], buildResults);
|
|
||||||
} else {
|
|
||||||
console.log('WARNING : No started emulators found, starting an emulator.');
|
|
||||||
return emulator.best_image()
|
|
||||||
.then(function(best_avd) {
|
|
||||||
if(best_avd) {
|
|
||||||
return emulator.start(best_avd.name)
|
|
||||||
.then(function(emulator_ID) {
|
|
||||||
console.log('WARNING : No target specified, deploying to emulator \'' + emulator_ID + '\'.');
|
|
||||||
return emulator.install(emulator_ID, buildResults);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return emulator.start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).then(function() {
|
||||||
|
if (install_target == '--device') {
|
||||||
|
return device.resolveTarget(null);
|
||||||
|
} else if (install_target == '--emulator') {
|
||||||
|
// Give preference to any already started emulators. Else, start one.
|
||||||
|
return emulator.list_started()
|
||||||
|
.then(function(started) {
|
||||||
|
return started && started.length > 0 ? started[0] : emulator.start();
|
||||||
|
}).then(function(emulatorId) {
|
||||||
|
return emulator.resolveTarget(emulatorId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// They specified a specific device/emulator ID.
|
||||||
|
return device.list()
|
||||||
|
.then(function(devices) {
|
||||||
|
if (devices.indexOf(install_target) > -1) {
|
||||||
|
return device.resolveTarget(install_target);
|
||||||
|
}
|
||||||
|
return emulator.list_started()
|
||||||
|
.then(function(started_emulators) {
|
||||||
|
if (started_emulators.indexOf(install_target) > -1) {
|
||||||
|
return emulator.resolveTarget(install_target);
|
||||||
|
}
|
||||||
|
return emulator.list_images()
|
||||||
|
.then(function(avds) {
|
||||||
|
// if target emulator isn't started, then start it.
|
||||||
|
for (avd in avds) {
|
||||||
|
if (avds[avd].name == install_target) {
|
||||||
|
return emulator.start(install_target)
|
||||||
|
.then(function(emulatorId) {
|
||||||
|
return emulator.resolveTarget(emulatorId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Q.reject('Target \'' + install_target + '\' not found, unable to run project');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function(resolvedTarget) {
|
||||||
|
return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
|
||||||
|
if (resolvedTarget.isEmulator) {
|
||||||
|
return emulator.install(resolvedTarget, buildResults);
|
||||||
|
}
|
||||||
|
return device.install(resolvedTarget, buildResults);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Coho updates this line:
|
// Coho updates this line:
|
||||||
var VERSION = "3.6.4";
|
var VERSION = "4.0.0-dev";
|
||||||
|
|
||||||
console.log(VERSION);
|
console.log(VERSION);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class __ACTIVITY__ extends CordovaActivity
|
|||||||
public void onCreate(Bundle savedInstanceState)
|
public void onCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
super.init();
|
|
||||||
// Set by <content src="index.html" /> in config.xml
|
// Set by <content src="index.html" /> in config.xml
|
||||||
loadUrl(launchUrl);
|
loadUrl(launchUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||||
android:hardwareAccelerated="true">
|
android:hardwareAccelerated="true" android:supportsRtl="true">
|
||||||
<activity android:name="__ACTIVITY__"
|
<activity android:name="__ACTIVITY__"
|
||||||
android:label="@string/activity_name"
|
android:label="@string/activity_name"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
import java.util.regex.Pattern
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GENERATED FILE! DO NOT EDIT!
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import groovy.swing.SwingBuilder
|
||||||
|
|
||||||
|
ext.cordova = {}
|
||||||
|
apply from: 'cordova.gradle', to: ext.cordova
|
||||||
apply plugin: 'android'
|
apply plugin: 'android'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
@@ -8,19 +32,16 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:0.10.+'
|
classpath 'com.android.tools.build:gradle:0.12.0+'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = '1.12'
|
||||||
|
}
|
||||||
|
|
||||||
ext.multiarch=false
|
ext.multiarch=false
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile fileTree(dir: 'libs', include: '*.jar')
|
|
||||||
for (subproject in getProjectList()) {
|
|
||||||
compile project(subproject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
@@ -35,11 +56,11 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode Integer.parseInt("" + getVersionCodeFromManifest() + "0")
|
versionCode Integer.parseInt(System.env.ANDROID_VERSION_CODE ?: ("" + getVersionCodeFromManifest() + "0"))
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdkVersion 19
|
compileSdkVersion cordova.cordovaSdkVersion
|
||||||
buildToolsVersion "19.0.0"
|
buildToolsVersion cordova.cordovaBuildToolsVersion
|
||||||
|
|
||||||
if (multiarch || System.env.BUILD_MULTIPLE_APKS) {
|
if (multiarch || System.env.BUILD_MULTIPLE_APKS) {
|
||||||
productFlavors {
|
productFlavors {
|
||||||
@@ -68,10 +89,78 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_1_7
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
// These must be set or Gradle will complain (even if they are overridden).
|
||||||
|
keyAlias = ""
|
||||||
|
keyPassword = ""
|
||||||
|
storeFile = null
|
||||||
|
storePassword = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSigningProps(System.env.RELEASE_SIGNING_PROPERTIES_FILE, signingConfigs.release)
|
||||||
|
}
|
||||||
|
if (System.env.DEBUG_SIGNING_PROPERTIES_FILE) {
|
||||||
|
addSigningProps(System.env.DEBUG_SIGNING_PROPERTIES_FILE, signingConfigs.debug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task wrapper(type: Wrapper) {
|
dependencies {
|
||||||
gradleVersion = '1.12'
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
// SUB-PROJECT DEPENDENCIES START
|
||||||
|
// SUB-PROJECT DEPENDENCIES END
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def promptForPassword(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 promptForReleaseKeyPassword() {
|
||||||
|
if (!System.env.RELEASE_SIGNING_PROPERTIES_FILE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!android.signingConfigs.release.storePassword) {
|
||||||
|
android.signingConfigs.release.storePassword = promptForPassword('Enter key store password: ')
|
||||||
|
println('set to:' + android.signingConfigs.release.storePassword)
|
||||||
|
}
|
||||||
|
if (!android.signingConfigs.release.keyPassword) {
|
||||||
|
android.signingConfigs.release.keyPassword = promptForPassword('Enter key password: ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.taskGraph.whenReady { taskGraph ->
|
||||||
|
taskGraph.getAllTasks().each() { task ->
|
||||||
|
if (task.name == 'validateReleaseSigning') {
|
||||||
|
promptForReleaseKeyPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getVersionCodeFromManifest() {
|
def getVersionCodeFromManifest() {
|
||||||
@@ -82,13 +171,42 @@ def getVersionCodeFromManifest() {
|
|||||||
return Integer.parseInt(matcher.group(1))
|
return Integer.parseInt(matcher.group(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
def getProjectList() {
|
def ensureValueExists(filePath, props, key) {
|
||||||
def manifestFile = file("project.properties")
|
if (props.get(key) == null) {
|
||||||
def pattern = Pattern.compile("android.library.reference.(\\d+)\\s*=\\s*(.*)")
|
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||||
def matcher = pattern.matcher(manifestFile.getText())
|
|
||||||
def projects = []
|
|
||||||
while (matcher.find()) {
|
|
||||||
projects.add(":" + matcher.group(2).replace("/",":"))
|
|
||||||
}
|
}
|
||||||
return projects
|
return props.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
def addSigningProps(propsFilePath, signingConfig) {
|
||||||
|
def propsFile = file(propsFilePath)
|
||||||
|
def props = new Properties()
|
||||||
|
propsFile.withReader { reader ->
|
||||||
|
props.load(reader)
|
||||||
|
}
|
||||||
|
def storeFile = new File(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 = ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||||
|
signingConfig.keyPassword = props.get('keyPassword')
|
||||||
|
signingConfig.storeFile = storeFile
|
||||||
|
signingConfig.storePassword = props.get('storePassword')
|
||||||
|
def storeType = props.get('storeType')
|
||||||
|
if (!storeType) {
|
||||||
|
def filename = storeFile.getName().toLowerCase();
|
||||||
|
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||||
|
storeType = 'pkcs12'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (storeType) {
|
||||||
|
signingConfig.storeType = storeType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file('build-extras.gradle').exists()) {
|
||||||
|
apply from: 'build-extras.gradle'
|
||||||
}
|
}
|
||||||
|
|||||||
120
bin/templates/project/cordova.gradle
Normal file
120
bin/templates/project/cordova.gradle
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
String getProjectTarget(String defaultTarget) {
|
||||||
|
def manifestFile = file("project.properties")
|
||||||
|
def pattern = Pattern.compile("target\\s*=\\s*(.*)")
|
||||||
|
def matcher = pattern.matcher(manifestFile.getText())
|
||||||
|
if (matcher.find()) {
|
||||||
|
matcher.group(1)
|
||||||
|
} else {
|
||||||
|
defaultTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] getAvailableBuildTools() {
|
||||||
|
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||||
|
buildToolsDir.list()
|
||||||
|
.findAll { it ==~ /[0-9.]+/ }
|
||||||
|
.sort { a, b -> compareVersions(b, a) }
|
||||||
|
}
|
||||||
|
|
||||||
|
String latestBuildToolsAvailable(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. Please 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
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.cordovaSdkVersion = System.env.MIN_SDK_VERSION ?: getProjectTarget("android-19")
|
||||||
|
ext.cordovaBuildToolsVersion = latestBuildToolsAvailable("19.1.0")
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<target name="-pre-compile">
|
|
||||||
<!-- Fix library references due to bug in build.xml: See: https://groups.google.com/forum/#!topic/android-developers/0ivH-YqCjzg -->
|
|
||||||
<pathconvert property="fixedJarsPath" refid="project.all.jars.path">
|
|
||||||
<filtermapper>
|
|
||||||
<replacestring from="/bin/" to="/ant-build/"/>
|
|
||||||
<replacestring from="\bin\" to="\ant-build\"/>
|
|
||||||
</filtermapper>
|
|
||||||
</pathconvert>
|
|
||||||
<path id="project.all.jars.path">
|
|
||||||
<pathelement path="${fixedJarsPath}"/>
|
|
||||||
</path>
|
|
||||||
<echo message="Set jars path to: ${toString:project.all.jars.path}"/>
|
|
||||||
</target>
|
|
||||||
<!-- Rename AndroidManifest.xml so that Eclipse's import wizard doesn't detect ant-build as a project -->
|
|
||||||
<target name="-post-build">
|
|
||||||
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
|
||||||
<move file="CordovaLib/ant-build/AndroidManifest.xml" tofile="CordovaLib/ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
|
||||||
</target>
|
|
||||||
</project>
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
def getProjectList() {
|
|
||||||
def manifestFile = file("project.properties")
|
|
||||||
def pattern = Pattern.compile("android.library.reference.(\\d+)\\s*=\\s*(.*)")
|
|
||||||
def matcher = pattern.matcher(manifestFile.getText())
|
|
||||||
def projects = []
|
|
||||||
while (matcher.find()) {
|
|
||||||
projects.add(":" + matcher.group(2).replace("/",":"))
|
|
||||||
}
|
|
||||||
return projects
|
|
||||||
}
|
|
||||||
|
|
||||||
for (subproject in getProjectList()) {
|
|
||||||
include subproject
|
|
||||||
}
|
|
||||||
|
|
||||||
include ':'
|
|
||||||
112
framework/assets/www/cordova.js
vendored
112
framework/assets/www/cordova.js
vendored
@@ -1,5 +1,5 @@
|
|||||||
// Platform: android
|
// Platform: android
|
||||||
// 8ca0f3b2b87e0759c5236b91c80f18438544409c
|
// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
|
||||||
/*
|
/*
|
||||||
Licensed to the Apache Software Foundation (ASF) under one
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
or more contributor license agreements. See the NOTICE file
|
or more contributor license agreements. See the NOTICE file
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
;(function() {
|
;(function() {
|
||||||
var PLATFORM_VERSION_BUILD_LABEL = '3.6.4';
|
var PLATFORM_VERSION_BUILD_LABEL = '3.7.0-dev';
|
||||||
// file: src/scripts/require.js
|
// file: src/scripts/require.js
|
||||||
|
|
||||||
/*jshint -W079 */
|
/*jshint -W079 */
|
||||||
@@ -263,11 +263,7 @@ var cordova = {
|
|||||||
* Called by native code when returning successful result from an action.
|
* Called by native code when returning successful result from an action.
|
||||||
*/
|
*/
|
||||||
callbackSuccess: function(callbackId, args) {
|
callbackSuccess: function(callbackId, args) {
|
||||||
try {
|
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
|
||||||
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error in success callback: " + callbackId + " = "+e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,30 +272,34 @@ var cordova = {
|
|||||||
callbackError: function(callbackId, args) {
|
callbackError: function(callbackId, args) {
|
||||||
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
|
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
|
||||||
// Derive success from status.
|
// Derive success from status.
|
||||||
try {
|
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
|
||||||
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error in error callback: " + callbackId + " = "+e);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by native code when returning the result from an action.
|
* Called by native code when returning the result from an action.
|
||||||
*/
|
*/
|
||||||
callbackFromNative: function(callbackId, success, status, args, keepCallback) {
|
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
|
||||||
var callback = cordova.callbacks[callbackId];
|
try {
|
||||||
if (callback) {
|
var callback = cordova.callbacks[callbackId];
|
||||||
if (success && status == cordova.callbackStatus.OK) {
|
if (callback) {
|
||||||
callback.success && callback.success.apply(null, args);
|
if (isSuccess && status == cordova.callbackStatus.OK) {
|
||||||
} else if (!success) {
|
callback.success && callback.success.apply(null, args);
|
||||||
callback.fail && callback.fail.apply(null, args);
|
} else {
|
||||||
}
|
callback.fail && callback.fail.apply(null, args);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear callback if not expecting any more results
|
// Clear callback if not expecting any more results
|
||||||
if (!keepCallback) {
|
if (!keepCallback) {
|
||||||
delete cordova.callbacks[callbackId];
|
delete cordova.callbacks[callbackId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
|
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
|
||||||
|
console && console.log && console.log(msg);
|
||||||
|
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addConstructor: function(func) {
|
addConstructor: function(func) {
|
||||||
channel.onCordovaReady.subscribe(function() {
|
channel.onCordovaReady.subscribe(function() {
|
||||||
@@ -1013,6 +1013,42 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function buildPayload(payload, message) {
|
||||||
|
var payloadKind = message.charAt(0);
|
||||||
|
if (payloadKind == 's') {
|
||||||
|
payload.push(message.slice(1));
|
||||||
|
} else if (payloadKind == 't') {
|
||||||
|
payload.push(true);
|
||||||
|
} else if (payloadKind == 'f') {
|
||||||
|
payload.push(false);
|
||||||
|
} else if (payloadKind == 'N') {
|
||||||
|
payload.push(null);
|
||||||
|
} else if (payloadKind == 'n') {
|
||||||
|
payload.push(+message.slice(1));
|
||||||
|
} else if (payloadKind == 'A') {
|
||||||
|
var data = message.slice(1);
|
||||||
|
var bytes = window.atob(data);
|
||||||
|
var arraybuffer = new Uint8Array(bytes.length);
|
||||||
|
for (var i = 0; i < bytes.length; i++) {
|
||||||
|
arraybuffer[i] = bytes.charCodeAt(i);
|
||||||
|
}
|
||||||
|
payload.push(arraybuffer.buffer);
|
||||||
|
} else if (payloadKind == 'S') {
|
||||||
|
payload.push(window.atob(message.slice(1)));
|
||||||
|
} else if (payloadKind == 'M') {
|
||||||
|
var multipartMessages = message.slice(1);
|
||||||
|
while (multipartMessages !== "") {
|
||||||
|
var spaceIdx = multipartMessages.indexOf(' ');
|
||||||
|
var msgLen = +multipartMessages.slice(0, spaceIdx);
|
||||||
|
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
|
||||||
|
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
|
||||||
|
buildPayload(payload, multipartMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload.push(JSON.parse(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
||||||
function processMessage(message) {
|
function processMessage(message) {
|
||||||
try {
|
try {
|
||||||
@@ -1026,32 +1062,10 @@ function processMessage(message) {
|
|||||||
var status = +message.slice(2, spaceIdx);
|
var status = +message.slice(2, spaceIdx);
|
||||||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
||||||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
||||||
var payloadKind = message.charAt(nextSpaceIdx + 1);
|
var payloadMessage = message.slice(nextSpaceIdx + 1);
|
||||||
var payload;
|
var payload = [];
|
||||||
if (payloadKind == 's') {
|
buildPayload(payload, payloadMessage);
|
||||||
payload = message.slice(nextSpaceIdx + 2);
|
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
|
||||||
} else if (payloadKind == 't') {
|
|
||||||
payload = true;
|
|
||||||
} else if (payloadKind == 'f') {
|
|
||||||
payload = false;
|
|
||||||
} else if (payloadKind == 'N') {
|
|
||||||
payload = null;
|
|
||||||
} else if (payloadKind == 'n') {
|
|
||||||
payload = +message.slice(nextSpaceIdx + 2);
|
|
||||||
} else if (payloadKind == 'A') {
|
|
||||||
var data = message.slice(nextSpaceIdx + 2);
|
|
||||||
var bytes = window.atob(data);
|
|
||||||
var arraybuffer = new Uint8Array(bytes.length);
|
|
||||||
for (var i = 0; i < bytes.length; i++) {
|
|
||||||
arraybuffer[i] = bytes.charCodeAt(i);
|
|
||||||
}
|
|
||||||
payload = arraybuffer.buffer;
|
|
||||||
} else if (payloadKind == 'S') {
|
|
||||||
payload = window.atob(message.slice(nextSpaceIdx + 2));
|
|
||||||
} else {
|
|
||||||
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
|
|
||||||
}
|
|
||||||
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
|
|
||||||
} else {
|
} else {
|
||||||
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
/*
|
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
Licensed to the Apache Software Foundation (ASF) under one
|
or more contributor license agreements. See the NOTICE file
|
||||||
or more contributor license agreements. See the NOTICE file
|
distributed with this work for additional information
|
||||||
distributed with this work for additional information
|
regarding copyright ownership. The ASF licenses this file
|
||||||
regarding copyright ownership. The ASF licenses this file
|
to you under the Apache License, Version 2.0 (the
|
||||||
to you under the Apache License, Version 2.0 (the
|
"License"); you may not use this file except in compliance
|
||||||
"License"); you may not use this file except in compliance
|
with the License. You may obtain a copy of the License at
|
||||||
with the License. You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing,
|
Unless required by applicable law or agreed to in writing,
|
||||||
software distributed under the License is distributed on an
|
software distributed under the License is distributed on an
|
||||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
KIND, either express or implied. See the License for the
|
KIND, either express or implied. See the License for the
|
||||||
specific language governing permissions and limitations
|
specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -25,15 +24,25 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:0.10.+'
|
// This should be updated with each cordova-android release.
|
||||||
|
// It can affect things like where the .apk is generated.
|
||||||
|
// It also dictates what the minimum android build-tools version
|
||||||
|
// that you need (Set in bin/templates/project/cordova.gradle).
|
||||||
|
// Make sure the value is the same in all locations:
|
||||||
|
// * framework/build.gradle
|
||||||
|
// * bin/templates/project/cordova.gradle
|
||||||
|
// * bin/templates/cordova/lib/plugin-build.gradle
|
||||||
|
// * distributionUrl within bin/templates/cordova/lib/build.js.
|
||||||
|
classpath 'com.android.tools.build:gradle:0.12.+'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'android-library'
|
apply plugin: 'android-library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 19
|
compileSdkVersion cordova.cordovaSdkVersion
|
||||||
buildToolsVersion "19.0.0"
|
buildToolsVersion cordova.cordovaBuildToolsVersion
|
||||||
|
publishNonDefault true
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_7
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<content src="index.html" />
|
<content src="index.html" />
|
||||||
|
|
||||||
<preference name="loglevel" value="DEBUG" />
|
<preference name="loglevel" value="DEBUG" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<preference name="splashscreen" value="resourceName" />
|
<preference name="splashscreen" value="resourceName" />
|
||||||
<preference name="backgroundColor" value="0xFFF" />
|
<preference name="backgroundColor" value="0xFFF" />
|
||||||
|
|||||||
@@ -54,35 +54,28 @@ import android.widget.RelativeLayout;
|
|||||||
* @see CordovaWebViewClient
|
* @see CordovaWebViewClient
|
||||||
* @see CordovaWebView
|
* @see CordovaWebView
|
||||||
*/
|
*/
|
||||||
public class CordovaChromeClient extends WebChromeClient {
|
public class AndroidChromeClient extends WebChromeClient {
|
||||||
|
|
||||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
public static final int FILECHOOSER_RESULTCODE = 5173;
|
||||||
private String TAG = "CordovaLog";
|
private static final String LOG_TAG = "AndroidChromeClient";
|
||||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||||
protected CordovaInterface cordova;
|
protected final CordovaInterface cordova;
|
||||||
protected CordovaWebView appView;
|
protected final AndroidWebView appView;
|
||||||
|
|
||||||
// the video progress view
|
// the video progress view
|
||||||
private View mVideoProgressView;
|
private View mVideoProgressView;
|
||||||
|
|
||||||
// File Chooser
|
//Keep track of last AlertDialog showed
|
||||||
public ValueCallback<Uri> mUploadMessage;
|
private AlertDialog lastHandledDialog;
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public CordovaChromeClient(CordovaInterface cordova) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
// File Chooser
|
||||||
|
protected ValueCallback<Uri> mUploadMessage;
|
||||||
|
|
||||||
|
public AndroidChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||||
this.cordova = ctx;
|
this.cordova = ctx;
|
||||||
this.appView = app;
|
this.appView = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public void setWebView(CordovaWebView view) {
|
|
||||||
this.appView = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell the client to display a javascript alert dialog.
|
* Tell the client to display a javascript alert dialog.
|
||||||
*
|
*
|
||||||
@@ -123,7 +116,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dlg.show();
|
lastHandledDialog = dlg.show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +165,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dlg.show();
|
lastHandledDialog = dlg.show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +209,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
res.cancel();
|
res.cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dlg.show();
|
lastHandledDialog = dlg.show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -228,7 +221,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||||
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||||
{
|
{
|
||||||
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +234,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
//This is only for Android 2.1
|
//This is only for Android 2.1
|
||||||
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
|
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
|
||||||
{
|
{
|
||||||
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
||||||
super.onConsoleMessage(message, lineNumber, sourceID);
|
super.onConsoleMessage(message, lineNumber, sourceID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +244,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||||
{
|
{
|
||||||
if (consoleMessage.message() != null)
|
if (consoleMessage.message() != null)
|
||||||
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||||
return super.onConsoleMessage(consoleMessage);
|
return super.onConsoleMessage(consoleMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +303,7 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||||
this.openFileChooser(uploadMsg, "*/*");
|
this.openFileChooser(uploadMsg, "*/*");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
||||||
this.openFileChooser(uploadMsg, acceptType, null);
|
this.openFileChooser(uploadMsg, acceptType, null);
|
||||||
}
|
}
|
||||||
@@ -324,8 +317,11 @@ public class CordovaChromeClient extends WebChromeClient {
|
|||||||
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
||||||
FILECHOOSER_RESULTCODE);
|
FILECHOOSER_RESULTCODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueCallback<Uri> getValueCallback() {
|
public void destroyLastDialog(){
|
||||||
return this.mUploadMessage;
|
if(lastHandledDialog != null){
|
||||||
|
lastHandledDialog.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
50
framework/src/org/apache/cordova/AndroidExposedJsApi.java
Executable file
50
framework/src/org/apache/cordova/AndroidExposedJsApi.java
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
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.JavascriptInterface;
|
||||||
|
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 AndroidExposedJsApi implements ExposedJsApi {
|
||||||
|
private final CordovaBridge bridge;
|
||||||
|
|
||||||
|
AndroidExposedJsApi(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
765
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
765
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
@@ -0,0 +1,765 @@
|
|||||||
|
/*
|
||||||
|
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.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.webkit.WebBackForwardList;
|
||||||
|
import android.webkit.WebHistoryItem;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class is our web view.
|
||||||
|
*
|
||||||
|
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
||||||
|
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
|
||||||
|
*/
|
||||||
|
public class AndroidWebView extends WebView implements CordovaWebView {
|
||||||
|
|
||||||
|
public static final String TAG = "AndroidWebView";
|
||||||
|
|
||||||
|
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||||
|
|
||||||
|
PluginManager pluginManager;
|
||||||
|
|
||||||
|
private BroadcastReceiver receiver;
|
||||||
|
|
||||||
|
|
||||||
|
/** Activities and other important classes **/
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
AndroidWebViewClient viewClient;
|
||||||
|
private AndroidChromeClient chromeClient;
|
||||||
|
|
||||||
|
// Flag to track that a loadUrl timeout occurred
|
||||||
|
int loadUrlTimeout = 0;
|
||||||
|
|
||||||
|
private long lastMenuEventTime = 0;
|
||||||
|
|
||||||
|
CordovaBridge bridge;
|
||||||
|
|
||||||
|
/** custom view created by the browser (a video player for example) */
|
||||||
|
private View mCustomView;
|
||||||
|
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||||
|
|
||||||
|
private CordovaResourceApi resourceApi;
|
||||||
|
private CordovaPreferences preferences;
|
||||||
|
private CordovaUriHelper helper;
|
||||||
|
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||||
|
String loadedUrl;
|
||||||
|
|
||||||
|
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
|
||||||
|
new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
Gravity.CENTER);
|
||||||
|
|
||||||
|
/** Used when created via reflection. */
|
||||||
|
public AndroidWebView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Required to allow view to be used within XML layouts. */
|
||||||
|
public AndroidWebView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use two-phase init so that the control will work with XML layouts.
|
||||||
|
@Override
|
||||||
|
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||||
|
CordovaPreferences preferences) {
|
||||||
|
if (this.cordova != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.helper = new CordovaUriHelper(cordova, this);
|
||||||
|
|
||||||
|
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||||
|
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||||
|
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), helper);
|
||||||
|
pluginManager.addService("App", "org.apache.cordova.CoreAndroid");
|
||||||
|
initWebViewSettings();
|
||||||
|
|
||||||
|
if (this.viewClient == null) {
|
||||||
|
setWebViewClient(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ?
|
||||||
|
new AndroidWebViewClient(cordova, this) :
|
||||||
|
new IceCreamCordovaWebViewClient(cordova, this));
|
||||||
|
}
|
||||||
|
if (this.chromeClient == null) {
|
||||||
|
setWebChromeClient(new AndroidChromeClient(cordova, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
exposeJsInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void initWebViewSettings() {
|
||||||
|
this.setInitialScale(0);
|
||||||
|
this.setVerticalScrollBarEnabled(false);
|
||||||
|
// TODO: The Activity is the one that should call requestFocus().
|
||||||
|
if (shouldRequestFocusOnInit()) {
|
||||||
|
this.requestFocusFromTouch();
|
||||||
|
}
|
||||||
|
this.setInitialScale(0);
|
||||||
|
this.setVerticalScrollBarEnabled(false);
|
||||||
|
if (shouldRequestFocusOnInit()) {
|
||||||
|
this.requestFocusFromTouch();
|
||||||
|
}
|
||||||
|
// Enable JavaScript
|
||||||
|
final WebSettings settings = this.getSettings();
|
||||||
|
settings.setJavaScriptEnabled(true);
|
||||||
|
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||||
|
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||||
|
|
||||||
|
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
|
||||||
|
try {
|
||||||
|
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
|
||||||
|
|
||||||
|
String manufacturer = android.os.Build.MANUFACTURER;
|
||||||
|
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||||
|
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
||||||
|
android.os.Build.MANUFACTURER.contains("HTC"))
|
||||||
|
{
|
||||||
|
gingerbread_getMethod.invoke(settings, true);
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(TAG, "Doing the NavDump failed with bad arguments");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//We don't save any form data in the application
|
||||||
|
settings.setSaveFormData(false);
|
||||||
|
settings.setSavePassword(false);
|
||||||
|
|
||||||
|
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
|
||||||
|
// while we do this
|
||||||
|
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||||
|
Level16Apis.enableUniversalAccess(settings);
|
||||||
|
// Enable database
|
||||||
|
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
||||||
|
String databasePath = 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 = getContext().getApplicationContext().getApplicationInfo();
|
||||||
|
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
|
||||||
|
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||||
|
enableRemoteDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.setGeolocationDatabasePath(databasePath);
|
||||||
|
|
||||||
|
// Enable DOM storage
|
||||||
|
settings.setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
// Enable built-in geolocation
|
||||||
|
settings.setGeolocationEnabled(true);
|
||||||
|
|
||||||
|
// Enable AppCache
|
||||||
|
// Fix for CB-2282
|
||||||
|
settings.setAppCacheMaxSize(5 * 1048576);
|
||||||
|
settings.setAppCachePath(databasePath);
|
||||||
|
settings.setAppCacheEnabled(true);
|
||||||
|
|
||||||
|
// Fix for CB-1405
|
||||||
|
// Google issue 4641
|
||||||
|
settings.getUserAgentString();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getContext().registerReceiver(this.receiver, intentFilter);
|
||||||
|
}
|
||||||
|
// end CB-1405
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
private void enableRemoteDebugging() {
|
||||||
|
try {
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method to decide whether or not you need to request the
|
||||||
|
* focus when your application start
|
||||||
|
*
|
||||||
|
* @return true unless this method is overriden to return a different value
|
||||||
|
*/
|
||||||
|
protected boolean shouldRequestFocusOnInit() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exposeJsInterface() {
|
||||||
|
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
|
||||||
|
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
|
||||||
|
// Bug being that Java Strings do not get converted to JS strings automatically.
|
||||||
|
// This isn't hard to work-around on the JS side, but it's easier to just
|
||||||
|
// use the prompt bridge instead.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AndroidExposedJsApi exposedJsApi = new AndroidExposedJsApi(bridge);
|
||||||
|
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebViewClient(WebViewClient client) {
|
||||||
|
this.viewClient = (AndroidWebViewClient)client;
|
||||||
|
super.setWebViewClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebChromeClient(WebChromeClient client) {
|
||||||
|
this.chromeClient = (AndroidChromeClient)client;
|
||||||
|
super.setWebChromeClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
this.loadUrlIntoView(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||||
|
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||||
|
this.loadUrlNow(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||||
|
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||||
|
|
||||||
|
if (recreatePlugins) {
|
||||||
|
this.loadedUrl = url;
|
||||||
|
this.pluginManager.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a timeout timer for loadUrl
|
||||||
|
final AndroidWebView me = this;
|
||||||
|
final int currentLoadUrlTimeout = me.loadUrlTimeout;
|
||||||
|
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||||
|
|
||||||
|
// Timeout error method
|
||||||
|
final Runnable loadError = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
me.stopLoading();
|
||||||
|
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||||
|
if (viewClient != null) {
|
||||||
|
viewClient.onReceivedError(AndroidWebView.this, -6, "The connection to the server was unsuccessful.", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 (me.loadUrlTimeout == currentLoadUrlTimeout) {
|
||||||
|
me.cordova.getActivity().runOnUiThread(loadError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load url
|
||||||
|
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
cordova.getThreadPool().execute(timeoutCheck);
|
||||||
|
me.loadUrlNow(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load URL in webview.
|
||||||
|
*/
|
||||||
|
private void loadUrlNow(String url) {
|
||||||
|
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
|
||||||
|
LOG.d(TAG, ">>> loadUrlNow()");
|
||||||
|
}
|
||||||
|
if (url.startsWith("javascript:") || helper.shouldAllowNavigation(url)) {
|
||||||
|
super.loadUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopLoading() {
|
||||||
|
//viewClient.isCurrentlyLoading = false;
|
||||||
|
super.stopLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onScrollChanged(int l, int t, int oldl, int oldt)
|
||||||
|
{
|
||||||
|
super.onScrollChanged(l, t, oldl, oldt);
|
||||||
|
//We should post a message that the scroll changed
|
||||||
|
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
|
||||||
|
pluginManager.postMessage("onScrollChanged", myEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send JavaScript statement back to JavaScript.
|
||||||
|
* (This is a convenience method)
|
||||||
|
*/
|
||||||
|
public void sendJavascript(String statement) {
|
||||||
|
bridge.getMessageQueue().addJavaScript(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a plugin result back to JavaScript.
|
||||||
|
*/
|
||||||
|
public void sendPluginResult(PluginResult result, String callbackId) {
|
||||||
|
bridge.getMessageQueue().addPluginResult(result, callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to previous page in history. (We manage our own history)
|
||||||
|
*
|
||||||
|
* @return true if we went back, false if we are already at top
|
||||||
|
*/
|
||||||
|
public boolean backHistory() {
|
||||||
|
|
||||||
|
// Check webview first to see if there is a history
|
||||||
|
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
|
||||||
|
if (super.canGoBack()) {
|
||||||
|
printBackForwardList();
|
||||||
|
super.goBack();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||||
|
*
|
||||||
|
* NOTE: If openExternal is false, only URLs listed in whitelist 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
|
||||||
|
*/
|
||||||
|
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
|
||||||
|
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
|
||||||
|
|
||||||
|
// If clearing history
|
||||||
|
if (clearHistory) {
|
||||||
|
this.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If loading into our webview
|
||||||
|
if (!openExternal) {
|
||||||
|
|
||||||
|
// Make sure url is in whitelist
|
||||||
|
if (helper.shouldAllowNavigation(url)) {
|
||||||
|
// TODO: What about params?
|
||||||
|
// Load new URL
|
||||||
|
loadUrlIntoView(url, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Load in default viewer if not
|
||||||
|
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 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.
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* onKeyDown
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||||
|
{
|
||||||
|
if(boundKeyCodes.contains(keyCode))
|
||||||
|
{
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(keyCode == KeyEvent.KEYCODE_MENU)
|
||||||
|
{
|
||||||
|
//How did we get here? Is there a childView?
|
||||||
|
View childView = this.getFocusedChild();
|
||||||
|
if(childView != null)
|
||||||
|
{
|
||||||
|
//Make sure we close the keyboard if it's present
|
||||||
|
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
|
||||||
|
cordova.getActivity().openOptionsMenu();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event)
|
||||||
|
{
|
||||||
|
// If back key
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
// A custom view is currently displayed (e.g. playing a video)
|
||||||
|
if(mCustomView != null) {
|
||||||
|
this.hideCustomView();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// The webview is currently displayed
|
||||||
|
// If back key is bound, then send event to JavaScript
|
||||||
|
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
|
||||||
|
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// If not bound
|
||||||
|
// Go to previous page in webview if it is possible to go back
|
||||||
|
if (this.backHistory()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If not, then invoke default behavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Legacy
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||||
|
if (this.lastMenuEventTime < event.getEventTime()) {
|
||||||
|
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
|
||||||
|
}
|
||||||
|
this.lastMenuEventTime = event.getEventTime();
|
||||||
|
return super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
// If search key
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||||
|
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Does webkit change this behavior?
|
||||||
|
return super.onKeyUp(keyCode, 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:
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePause(boolean keepRunning)
|
||||||
|
{
|
||||||
|
LOG.d(TAG, "Handle the pause");
|
||||||
|
// Send pause event to JavaScript
|
||||||
|
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onPause(keepRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If app doesn't want to run in background
|
||||||
|
if (!keepRunning) {
|
||||||
|
// Pause JavaScript timers (including setInterval)
|
||||||
|
this.pauseTimers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
|
||||||
|
{
|
||||||
|
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onResume(keepRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume JavaScript timers (including setInterval)
|
||||||
|
this.resumeTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDestroy()
|
||||||
|
{
|
||||||
|
// Send destroy event to JavaScript
|
||||||
|
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
||||||
|
|
||||||
|
// Load blank page so that JavaScript onunload is called
|
||||||
|
this.loadUrl("about:blank");
|
||||||
|
|
||||||
|
//Remove last AlertDialog
|
||||||
|
this.chromeClient.destroyLastDialog();
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregister the receiver
|
||||||
|
if (this.receiver != null) {
|
||||||
|
try {
|
||||||
|
getContext().unregisterReceiver(this.receiver);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewIntent(Intent intent)
|
||||||
|
{
|
||||||
|
//Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapping these functions in their own class prevents warnings in adb like:
|
||||||
|
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
|
||||||
|
@TargetApi(16)
|
||||||
|
private static class Level16Apis {
|
||||||
|
static void enableUniversalAccess(WebSettings settings) {
|
||||||
|
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printBackForwardList() {
|
||||||
|
WebBackForwardList currentList = this.copyBackForwardList();
|
||||||
|
int currentSize = currentList.getSize();
|
||||||
|
for(int i = 0; i < currentSize; ++i)
|
||||||
|
{
|
||||||
|
WebHistoryItem item = currentList.getItemAtIndex(i);
|
||||||
|
String url = item.getUrl();
|
||||||
|
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Can Go Back is BROKEN!
|
||||||
|
public boolean startOfHistory()
|
||||||
|
{
|
||||||
|
WebBackForwardList currentList = this.copyBackForwardList();
|
||||||
|
WebHistoryItem item = currentList.getItemAtIndex(0);
|
||||||
|
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
|
||||||
|
String url = item.getUrl();
|
||||||
|
String currentUrl = this.getUrl();
|
||||||
|
LOG.d(TAG, "The current URL is: " + currentUrl);
|
||||||
|
LOG.d(TAG, "The URL at item 0 is: " + url);
|
||||||
|
return currentUrl.equals(url);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) this.getParent();
|
||||||
|
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
|
||||||
|
|
||||||
|
// Hide the content view.
|
||||||
|
this.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Finally show the custom view container.
|
||||||
|
parent.setVisibility(View.VISIBLE);
|
||||||
|
parent.bringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideCustomView() {
|
||||||
|
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||||
|
Log.d(TAG, "Hiding Custom View");
|
||||||
|
if (mCustomView == null) return;
|
||||||
|
|
||||||
|
// Hide the custom view.
|
||||||
|
mCustomView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Remove the custom view from its container.
|
||||||
|
ViewGroup parent = (ViewGroup) this.getParent();
|
||||||
|
parent.removeView(mCustomView);
|
||||||
|
mCustomView = null;
|
||||||
|
mCustomViewCallback.onCustomViewHidden();
|
||||||
|
|
||||||
|
// Show the content view.
|
||||||
|
this.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if the video overlay is showing then we need to know
|
||||||
|
* as it effects back button handling
|
||||||
|
*
|
||||||
|
* @return true if custom view is showing
|
||||||
|
*/
|
||||||
|
public boolean isCustomViewShowing() {
|
||||||
|
return mCustomView != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebBackForwardList restoreState(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
WebBackForwardList myList = super.restoreState(savedInstanceState);
|
||||||
|
Log.d(TAG, "WebView restoration crew now restoring!");
|
||||||
|
//Initialize the plugin manager once more
|
||||||
|
this.pluginManager.init();
|
||||||
|
return myList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CordovaResourceApi getResourceApi() {
|
||||||
|
return resourceApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPageReset() {
|
||||||
|
boundKeyCodes.clear();
|
||||||
|
pluginManager.onReset();
|
||||||
|
bridge.reset(loadedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PluginManager getPluginManager() {
|
||||||
|
return this.pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CordovaPreferences getPreferences() {
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFilePickerResult(Uri uri) {
|
||||||
|
if (null == chromeClient.mUploadMessage)
|
||||||
|
return;
|
||||||
|
chromeClient.mUploadMessage.onReceiveValue(uri);
|
||||||
|
chromeClient.mUploadMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postMessage(String id, Object data) {
|
||||||
|
return pluginManager.postMessage(id, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,9 +20,6 @@ package org.apache.cordova;
|
|||||||
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
|
||||||
import org.apache.cordova.CordovaInterface;
|
|
||||||
|
|
||||||
import org.apache.cordova.LOG;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@@ -38,6 +35,7 @@ import android.webkit.SslErrorHandler;
|
|||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the WebViewClient that implements callbacks for our web view.
|
* 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
|
* The kind of callbacks that happen here are regarding the rendering of the
|
||||||
@@ -50,42 +48,20 @@ import android.webkit.WebViewClient;
|
|||||||
* @see CordovaChromeClient
|
* @see CordovaChromeClient
|
||||||
* @see CordovaWebView
|
* @see CordovaWebView
|
||||||
*/
|
*/
|
||||||
public class CordovaWebViewClient extends WebViewClient {
|
public class AndroidWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
private static final String TAG = "CordovaWebViewClient";
|
private static final String TAG = "AndroidWebViewClient";
|
||||||
CordovaInterface cordova;
|
protected final CordovaInterface cordova;
|
||||||
CordovaWebView appView;
|
protected final AndroidWebView appView;
|
||||||
CordovaUriHelper helper;
|
protected final CordovaUriHelper helper;
|
||||||
private boolean doClearHistory = false;
|
private boolean doClearHistory = false;
|
||||||
boolean isCurrentlyLoading;
|
boolean isCurrentlyLoading;
|
||||||
|
|
||||||
/** The authorization tokens. */
|
/** The authorization tokens. */
|
||||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||||
|
|
||||||
@Deprecated
|
public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||||
public CordovaWebViewClient(CordovaInterface cordova) {
|
|
||||||
this.cordova = cordova;
|
this.cordova = cordova;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param cordova
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
public CordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
this.appView = view;
|
|
||||||
helper = new CordovaUriHelper(cordova, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setWebView(CordovaWebView view) {
|
|
||||||
this.appView = view;
|
this.appView = view;
|
||||||
helper = new CordovaUriHelper(cordova, view);
|
helper = new CordovaUriHelper(cordova, view);
|
||||||
}
|
}
|
||||||
@@ -100,17 +76,12 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
return helper.shouldOverrideUrlLoading(view, url);
|
return helper.shouldOverrideUrlLoading(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On received http auth request.
|
* 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
|
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
* @param handler
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||||
@@ -140,16 +111,11 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
super.onPageStarted(view, url, favicon);
|
super.onPageStarted(view, url, favicon);
|
||||||
isCurrentlyLoading = true;
|
isCurrentlyLoading = true;
|
||||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
LOG.d(TAG, "onPageStarted(" + url + ")");
|
||||||
// Flush stale messages.
|
// Flush stale messages & reset plugins.
|
||||||
this.appView.bridge.reset(url);
|
this.appView.onPageReset();
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
// Broadcast message that page has loaded
|
||||||
this.appView.postMessage("onPageStarted", url);
|
this.appView.getPluginManager().postMessage("onPageStarted", url);
|
||||||
|
|
||||||
// Notify all plugins of the navigation, so they can clean up if necessary.
|
|
||||||
if (this.appView.pluginManager != null) {
|
|
||||||
this.appView.pluginManager.onReset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,10 +148,10 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear timeout flag
|
// Clear timeout flag
|
||||||
this.appView.loadUrlTimeout++;
|
appView.loadUrlTimeout++;
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
// Broadcast message that page has loaded
|
||||||
this.appView.postMessage("onPageFinished", url);
|
this.appView.getPluginManager().postMessage("onPageFinished", url);
|
||||||
|
|
||||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||||
if (this.appView.getVisibility() == View.INVISIBLE) {
|
if (this.appView.getVisibility() == View.INVISIBLE) {
|
||||||
@@ -195,7 +161,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
appView.postMessage("spinner", "stop");
|
appView.getPluginManager().postMessage("spinner", "stop");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -207,7 +173,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
|
|
||||||
// Shutdown if blank loaded
|
// Shutdown if blank loaded
|
||||||
if (url.equals("about:blank")) {
|
if (url.equals("about:blank")) {
|
||||||
appView.postMessage("exit", null);
|
appView.getPluginManager().postMessage("exit", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +195,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||||
|
|
||||||
// Clear timeout flag
|
// Clear timeout flag
|
||||||
this.appView.loadUrlTimeout++;
|
appView.loadUrlTimeout++;
|
||||||
|
|
||||||
// If this is a "Protocol Not Supported" error, then revert to the previous
|
// 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
|
// page. If there was no previous page, then punt. The application's config
|
||||||
@@ -252,7 +218,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
this.appView.postMessage("onReceivedError", data);
|
this.appView.getPluginManager().postMessage("onReceivedError", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,5 +327,4 @@ public class CordovaWebViewClient extends WebViewClient {
|
|||||||
public void clearAuthenticationTokens() {
|
public void clearAuthenticationTokens() {
|
||||||
this.authenticationTokens.clear();
|
this.authenticationTokens.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -46,48 +46,6 @@ public class Config {
|
|||||||
parser = new ConfigXmlParser();
|
parser = new ConfigXmlParser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add entry to approved list of URLs (whitelist)
|
|
||||||
*
|
|
||||||
* @param origin URL regular expression to allow
|
|
||||||
* @param subdomains T=include all subdomains under origin
|
|
||||||
*/
|
|
||||||
public static void addWhiteListEntry(String origin, boolean subdomains) {
|
|
||||||
if (parser == null) {
|
|
||||||
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
parser.getInternalWhitelist().addWhiteListEntry(origin, subdomains);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if URL is in approved list of URLs to load.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return true if whitelisted
|
|
||||||
*/
|
|
||||||
public static boolean isUrlWhiteListed(String url) {
|
|
||||||
if (parser == null) {
|
|
||||||
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return parser.getInternalWhitelist().isUrlWhiteListed(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if URL is in approved list of URLs to launch external applications.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return true if whitelisted
|
|
||||||
*/
|
|
||||||
public static boolean isUrlExternallyWhiteListed(String url) {
|
|
||||||
if (parser == null) {
|
|
||||||
Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return parser.getExternalWhitelist().isUrlWhiteListed(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStartUrl() {
|
public static String getStartUrl() {
|
||||||
if (parser == null) {
|
if (parser == null) {
|
||||||
@@ -100,14 +58,6 @@ public class Config {
|
|||||||
return parser.getPreferences().getString("errorurl", null);
|
return parser.getPreferences().getString("errorurl", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Whitelist getWhitelist() {
|
|
||||||
return parser.getInternalWhitelist();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Whitelist getExternalWhitelist() {
|
|
||||||
return parser.getExternalWhitelist();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<PluginEntry> getPluginEntries() {
|
public static List<PluginEntry> getPluginEntries() {
|
||||||
return parser.getPluginEntries();
|
return parser.getPluginEntries();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,25 +30,14 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class ConfigXmlParser {
|
public class ConfigXmlParser {
|
||||||
private static String TAG = "ConfigXmlParser";
|
private static String TAG = "ConfigXmlParser";
|
||||||
|
|
||||||
private String launchUrl = "file:///android_asset/www/index.html";
|
private String launchUrl = "file:///android_asset/www/index.html";
|
||||||
private CordovaPreferences prefs = new CordovaPreferences();
|
private CordovaPreferences prefs = new CordovaPreferences();
|
||||||
private Whitelist internalWhitelist = new Whitelist();
|
|
||||||
private Whitelist externalWhitelist = new Whitelist();
|
|
||||||
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
|
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
|
||||||
|
|
||||||
public Whitelist getInternalWhitelist() {
|
|
||||||
return internalWhitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Whitelist getExternalWhitelist() {
|
|
||||||
return externalWhitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaPreferences getPreferences() {
|
public CordovaPreferences getPreferences() {
|
||||||
return prefs;
|
return prefs;
|
||||||
}
|
}
|
||||||
@@ -60,7 +49,7 @@ public class ConfigXmlParser {
|
|||||||
public String getLaunchUrl() {
|
public String getLaunchUrl() {
|
||||||
return launchUrl;
|
return launchUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parse(Activity action) {
|
public void parse(Activity action) {
|
||||||
// First checking the class namespace for config.xml
|
// First checking the class namespace for config.xml
|
||||||
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
|
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
|
||||||
@@ -75,86 +64,20 @@ public class ConfigXmlParser {
|
|||||||
parse(action.getResources().getXml(id));
|
parse(action.getResources().getXml(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean insideFeature = false;
|
||||||
|
String service = "", pluginClass = "", paramType = "";
|
||||||
|
boolean onload = false;
|
||||||
|
|
||||||
public void parse(XmlResourceParser xml) {
|
public void parse(XmlResourceParser xml) {
|
||||||
int eventType = -1;
|
int eventType = -1;
|
||||||
String service = "", pluginClass = "", paramType = "";
|
|
||||||
boolean onload = false;
|
|
||||||
boolean insideFeature = false;
|
|
||||||
ArrayList<String> urlMap = null;
|
|
||||||
|
|
||||||
// Add implicitly allowed URLs
|
|
||||||
internalWhitelist.addWhiteListEntry("file:///*", false);
|
|
||||||
internalWhitelist.addWhiteListEntry("content:///*", false);
|
|
||||||
internalWhitelist.addWhiteListEntry("data:*", false);
|
|
||||||
|
|
||||||
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||||
if (eventType == XmlResourceParser.START_TAG) {
|
if (eventType == XmlResourceParser.START_TAG) {
|
||||||
String strNode = xml.getName();
|
handleStartTag(xml);
|
||||||
if (strNode.equals("url-filter")) {
|
|
||||||
Log.w(TAG, "Plugin " + service + " is using deprecated tag <url-filter>");
|
|
||||||
if (urlMap == null) {
|
|
||||||
urlMap = new ArrayList<String>(2);
|
|
||||||
}
|
|
||||||
urlMap.add(xml.getAttributeValue(null, "value"));
|
|
||||||
} else 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("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) {
|
|
||||||
externalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
|
||||||
} else {
|
|
||||||
if ("*".equals(origin)) {
|
|
||||||
// Special-case * origin to mean http and https when used for internal
|
|
||||||
// whitelist. This prevents external urls like sms: and geo: from being
|
|
||||||
// handled internally.
|
|
||||||
internalWhitelist.addWhiteListEntry("http://*/*", false);
|
|
||||||
internalWhitelist.addWhiteListEntry("https://*/*", false);
|
|
||||||
} else {
|
|
||||||
internalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (eventType == XmlResourceParser.END_TAG)
|
else if (eventType == XmlResourceParser.END_TAG)
|
||||||
{
|
{
|
||||||
String strNode = xml.getName();
|
handleEndTag(xml);
|
||||||
if (strNode.equals("feature")) {
|
|
||||||
pluginEntries.add(new PluginEntry(service, pluginClass, onload, urlMap));
|
|
||||||
|
|
||||||
service = "";
|
|
||||||
pluginClass = "";
|
|
||||||
insideFeature = false;
|
|
||||||
onload = false;
|
|
||||||
urlMap = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
eventType = xml.next();
|
eventType = xml.next();
|
||||||
@@ -166,6 +89,48 @@ public class ConfigXmlParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleStartTag(XmlResourceParser 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(XmlResourceParser 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) {
|
private void setStartUrl(String src) {
|
||||||
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
||||||
Matcher matcher = schemeRegex.matcher(src);
|
Matcher matcher = schemeRegex.matcher(src);
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@@ -29,7 +30,6 @@ import org.apache.cordova.LOG;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@@ -44,7 +44,6 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -52,7 +51,6 @@ import android.view.ViewGroup;
|
|||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.webkit.ValueCallback;
|
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
@@ -93,12 +91,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
// The webview for our app
|
// The webview for our app
|
||||||
protected CordovaWebView appView;
|
protected CordovaWebView appView;
|
||||||
|
|
||||||
@Deprecated // unused.
|
|
||||||
protected CordovaWebViewClient webViewClient;
|
|
||||||
|
|
||||||
@Deprecated // Will be removed. Use findViewById() to retrieve views.
|
|
||||||
protected LinearLayout root;
|
|
||||||
|
|
||||||
protected ProgressDialog spinnerDialog = null;
|
protected ProgressDialog spinnerDialog = null;
|
||||||
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
@@ -118,7 +110,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
// Draw a splash screen using an image located in the drawable resource directory.
|
// Draw a splash screen using an image located in the drawable resource directory.
|
||||||
// This is not the same as calling super.loadSplashscreen(url)
|
// This is not the same as calling super.loadSplashscreen(url)
|
||||||
protected int splashscreen = 0;
|
protected int splashscreen = 0;
|
||||||
protected int splashscreenTime = 3000;
|
|
||||||
|
|
||||||
// LoadUrl timeout value in msec (default of 20 sec)
|
// LoadUrl timeout value in msec (default of 20 sec)
|
||||||
protected int loadUrlTimeoutValue = 20000;
|
protected int loadUrlTimeoutValue = 20000;
|
||||||
@@ -132,69 +123,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
|
|
||||||
// Read from config.xml:
|
// Read from config.xml:
|
||||||
protected CordovaPreferences preferences;
|
protected CordovaPreferences preferences;
|
||||||
protected Whitelist internalWhitelist;
|
|
||||||
protected Whitelist externalWhitelist;
|
|
||||||
protected String launchUrl;
|
protected String launchUrl;
|
||||||
protected ArrayList<PluginEntry> pluginEntries;
|
protected ArrayList<PluginEntry> pluginEntries;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the authentication token.
|
|
||||||
*
|
|
||||||
* @param authenticationToken
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*/
|
|
||||||
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
return this.appView.viewClient.removeAuthenticationToken(host, realm);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
return this.appView.viewClient.getAuthenticationToken(host, realm);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all authentication tokens.
|
|
||||||
*/
|
|
||||||
public void clearAuthenticationTokens() {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
this.appView.viewClient.clearAuthenticationTokens();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is first created.
|
* Called when the activity is first created.
|
||||||
*/
|
*/
|
||||||
@@ -230,6 +161,20 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
initCallbackClass = savedInstanceState.getString("callbackClass");
|
initCallbackClass = savedInstanceState.getString("callbackClass");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void init() {
|
||||||
|
appView = makeWebView();
|
||||||
|
|
||||||
|
// TODO: Have the views set this themselves.
|
||||||
|
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||||
|
appView.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
}
|
||||||
|
createViews();
|
||||||
|
|
||||||
|
// TODO: Make this a preference (CB-6153)
|
||||||
|
// Setup the hardware volume controls to handle volume control
|
||||||
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
protected void loadConfig() {
|
protected void loadConfig() {
|
||||||
@@ -238,8 +183,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
preferences = parser.getPreferences();
|
preferences = parser.getPreferences();
|
||||||
preferences.setPreferencesBundle(getIntent().getExtras());
|
preferences.setPreferencesBundle(getIntent().getExtras());
|
||||||
preferences.copyIntoIntentExtras(this);
|
preferences.copyIntoIntentExtras(this);
|
||||||
internalWhitelist = parser.getInternalWhitelist();
|
|
||||||
externalWhitelist = parser.getExternalWhitelist();
|
|
||||||
launchUrl = parser.getLaunchUrl();
|
launchUrl = parser.getLaunchUrl();
|
||||||
pluginEntries = parser.getPluginEntries();
|
pluginEntries = parser.getPluginEntries();
|
||||||
Config.parser = parser;
|
Config.parser = parser;
|
||||||
@@ -248,35 +191,31 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
protected void createViews() {
|
protected void createViews() {
|
||||||
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
|
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
|
||||||
|
|
||||||
LOG.d(TAG, "CordovaActivity.createViews()");
|
|
||||||
|
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
int width = display.getWidth();
|
int width = display.getWidth();
|
||||||
int height = display.getHeight();
|
int height = display.getHeight();
|
||||||
|
|
||||||
root = new LinearLayoutSoftKeyboardDetect(this, width, height);
|
LinearLayoutSoftKeyboardDetect root = new LinearLayoutSoftKeyboardDetect(this, width, height);
|
||||||
root.setOrientation(LinearLayout.VERTICAL);
|
root.setOrientation(LinearLayout.VERTICAL);
|
||||||
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
|
ViewGroup.LayoutParams.MATCH_PARENT, 0.0F));
|
||||||
|
|
||||||
appView.setId(100);
|
appView.getView().setId(100);
|
||||||
appView.setLayoutParams(new LinearLayout.LayoutParams(
|
appView.getView().setLayoutParams(new LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
1.0F));
|
1.0F));
|
||||||
|
|
||||||
// Add web view but make it invisible while loading URL
|
// Add web view but make it invisible while loading URL
|
||||||
appView.setVisibility(View.INVISIBLE);
|
appView.getView().setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
// need to remove appView from any existing parent before invoking root.addView(appView)
|
// need to remove appView from any existing parent before invoking root.addView(appView)
|
||||||
ViewParent parent = appView.getParent();
|
ViewParent parent = appView.getView().getParent();
|
||||||
if ((parent != null) && (parent != root)) {
|
if ((parent != null) && (parent != root)) {
|
||||||
LOG.d(TAG, "removing appView from existing parent");
|
LOG.d(TAG, "removing appView from existing parent");
|
||||||
ViewGroup parentGroup = (ViewGroup) parent;
|
ViewGroup parentGroup = (ViewGroup) parent;
|
||||||
parentGroup.removeView(appView);
|
parentGroup.removeView(appView.getView());
|
||||||
}
|
}
|
||||||
root.addView((View) appView);
|
root.addView(appView.getView());
|
||||||
setContentView(root);
|
setContentView(root);
|
||||||
|
|
||||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||||
@@ -297,75 +236,50 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
* require a more specialized web view.
|
* require a more specialized web view.
|
||||||
*/
|
*/
|
||||||
protected CordovaWebView makeWebView() {
|
protected CordovaWebView makeWebView() {
|
||||||
return new CordovaWebView(CordovaActivity.this);
|
String r = preferences.getString("webView", null);
|
||||||
}
|
CordovaWebView ret = null;
|
||||||
|
if (r != null) {
|
||||||
/**
|
try {
|
||||||
* Construct the client for the default web view object.
|
Class<?> webViewClass = Class.forName(r);
|
||||||
*
|
Constructor<?> constructor = webViewClass.getConstructor(Context.class);
|
||||||
* This is intended to be overridable by subclasses of CordovaIntent which
|
ret = (CordovaWebView) constructor.newInstance((Context)this);
|
||||||
* require a more specialized web view.
|
} catch (ClassNotFoundException e) {
|
||||||
*
|
e.printStackTrace();
|
||||||
* @param webView the default constructed web view object
|
} catch (InstantiationException e) {
|
||||||
*/
|
e.printStackTrace();
|
||||||
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
|
} catch (IllegalAccessException e) {
|
||||||
return webView.makeWebViewClient(this);
|
e.printStackTrace();
|
||||||
}
|
} catch (IllegalArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
/**
|
} catch (InvocationTargetException e) {
|
||||||
* Construct the chrome client for the default web view object.
|
e.printStackTrace();
|
||||||
*
|
} catch (NoSuchMethodException e) {
|
||||||
* This is intended to be overridable by subclasses of CordovaIntent which
|
e.printStackTrace();
|
||||||
* require a more specialized web view.
|
}
|
||||||
*
|
|
||||||
* @param webView the default constructed web view object
|
|
||||||
*/
|
|
||||||
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
|
|
||||||
return webView.makeWebChromeClient(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
this.init(appView, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
@Deprecated // Call init() instead and override makeWebView() to customize.
|
|
||||||
public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
|
|
||||||
LOG.d(TAG, "CordovaActivity.init()");
|
|
||||||
|
|
||||||
appView = webView != null ? webView : makeWebView();
|
|
||||||
if (appView.pluginManager == null) {
|
|
||||||
appView.init(this, webViewClient != null ? webViewClient : makeWebViewClient(appView),
|
|
||||||
webChromeClient != null ? webChromeClient : makeChromeClient(appView),
|
|
||||||
pluginEntries, internalWhitelist, externalWhitelist, preferences);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Have the views set this themselves.
|
if (ret == null) {
|
||||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
// If all else fails, return a default WebView
|
||||||
appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
ret = new AndroidWebView(this);
|
||||||
}
|
}
|
||||||
createViews();
|
ret.init(this, pluginEntries, preferences);
|
||||||
|
return ret;
|
||||||
// TODO: Make this a preference (CB-6153)
|
|
||||||
// Setup the hardware volume controls to handle volume control
|
|
||||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the url into the webview.
|
* Load the url into the webview.
|
||||||
*/
|
*/
|
||||||
public void loadUrl(String url) {
|
public void loadUrl(String url, int splashscreenTime) {
|
||||||
if (appView == null) {
|
if (appView == null) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
this.splashscreenTime = preferences.getInteger("SplashScreenDelay", this.splashscreenTime);
|
|
||||||
String splash = preferences.getString("SplashScreen", null);
|
String splash = preferences.getString("SplashScreen", null);
|
||||||
if(this.splashscreenTime > 0 && splash != null)
|
if(splashscreenTime > 0 && splash != null)
|
||||||
{
|
{
|
||||||
this.splashscreen = getResources().getIdentifier(splash, "drawable", getClass().getPackage().getName());;
|
this.splashscreen = getResources().getIdentifier(splash, "drawable", getClass().getPackage().getName());;
|
||||||
if(this.splashscreen != 0)
|
if(this.splashscreen != 0)
|
||||||
{
|
{
|
||||||
this.showSplashScreen(this.splashscreenTime);
|
this.showSplashScreen(splashscreenTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,21 +287,17 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
||||||
|
|
||||||
//Check if the view is attached to anything
|
//Check if the view is attached to anything
|
||||||
if(appView.getParent() != null)
|
if(appView.getView().getParent() != null)
|
||||||
{
|
{
|
||||||
// Then load the spinner
|
// Then load the spinner
|
||||||
this.loadSpinner();
|
this.loadSpinner();
|
||||||
}
|
}
|
||||||
//Load the correct splashscreen
|
//Load the correct splashscreen
|
||||||
|
|
||||||
if(this.splashscreen != 0)
|
if(this.splashscreen != 0)
|
||||||
{
|
{
|
||||||
this.appView.loadUrl(url, this.splashscreenTime);
|
appView.getPluginManager().postMessage("splashscreen", "show");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.appView.loadUrl(url);
|
|
||||||
}
|
}
|
||||||
|
this.appView.loadUrlIntoView(url, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,10 +307,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
* @param url
|
* @param url
|
||||||
* @param time The number of ms to wait before loading webview
|
* @param time The number of ms to wait before loading webview
|
||||||
*/
|
*/
|
||||||
public void loadUrl(final String url, int time) {
|
public void loadUrl(final String url) {
|
||||||
|
if (appView == null) {
|
||||||
this.splashscreenTime = time;
|
init();
|
||||||
this.loadUrl(url);
|
}
|
||||||
|
this.loadUrl(url, preferences.getInteger("SplashScreenDelay", 3000));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -436,134 +347,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public void cancelLoadUrl() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the resource cache.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on appView directly.
|
|
||||||
public void clearCache() {
|
|
||||||
if (appView == null) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
this.appView.clearCache(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear web history in this web view.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on appView directly.
|
|
||||||
public void clearHistory() {
|
|
||||||
this.appView.clearHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to previous page in history. (We manage our own history)
|
|
||||||
*
|
|
||||||
* @return true if we went back, false if we are already at top
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on appView directly.
|
|
||||||
public boolean backHistory() {
|
|
||||||
if (this.appView != null) {
|
|
||||||
return appView.backHistory();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get boolean property for activity.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on preferences directly.
|
|
||||||
public boolean getBooleanProperty(String name, boolean defaultValue) {
|
|
||||||
return preferences.getBoolean(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get int property for activity.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on preferences directly.
|
|
||||||
public int getIntegerProperty(String name, int defaultValue) {
|
|
||||||
return preferences.getInteger(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get string property for activity.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on preferences directly.
|
|
||||||
public String getStringProperty(String name, String defaultValue) {
|
|
||||||
return preferences.getString(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get double property for activity.
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on preferences directly.
|
|
||||||
public double getDoubleProperty(String name, double defaultValue) {
|
|
||||||
return preferences.getDouble(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set boolean property on activity.
|
|
||||||
* This method has been deprecated in 3.0 and will be removed at a future
|
|
||||||
* time. Please use config.xml instead.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param value
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setBooleanProperty(String name, boolean value) {
|
|
||||||
Log.d(TAG, "Setting boolean properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
|
||||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set int property on activity.
|
|
||||||
* This method has been deprecated in 3.0 and will be removed at a future
|
|
||||||
* time. Please use config.xml instead.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param value
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setIntegerProperty(String name, int value) {
|
|
||||||
Log.d(TAG, "Setting integer properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
|
||||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set string property on activity.
|
|
||||||
* This method has been deprecated in 3.0 and will be removed at a future
|
|
||||||
* time. Please use config.xml instead.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param value
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setStringProperty(String name, String value) {
|
|
||||||
Log.d(TAG, "Setting string properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
|
||||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set double property on activity.
|
|
||||||
* This method has been deprecated in 3.0 and will be removed at a future
|
|
||||||
* time. Please use config.xml instead.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param value
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setDoubleProperty(String name, double value) {
|
|
||||||
Log.d(TAG, "Setting double properties in CordovaActivity will be deprecated in 3.0 on July 2013, please use config.xml");
|
|
||||||
this.getIntent().putExtra(name.toLowerCase(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the system is about to start resuming a previous activity.
|
* Called when the system is about to start resuming a previous activity.
|
||||||
*/
|
*/
|
||||||
@@ -653,41 +436,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to all plugins.
|
|
||||||
*/
|
|
||||||
public void postMessage(String id, Object data) {
|
|
||||||
if (this.appView != null) {
|
|
||||||
this.appView.postMessage(id, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Add services to res/xml/plugins.xml instead.
|
|
||||||
*
|
|
||||||
* Add a class that implements a service.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void addService(String serviceType, String className) {
|
|
||||||
if (this.appView != null && this.appView.pluginManager != null) {
|
|
||||||
this.appView.pluginManager.addService(serviceType, className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send JavaScript statement back to JavaScript.
|
|
||||||
* (This is a convenience method)
|
|
||||||
*
|
|
||||||
* @param statement
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on appView directly.
|
|
||||||
public void sendJavascript(String statement) {
|
|
||||||
if (this.appView != null) {
|
|
||||||
this.appView.bridge.getMessageQueue().addJavaScript(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the spinner. Must be called from the UI thread.
|
* Show the spinner. Must be called from the UI thread.
|
||||||
*
|
*
|
||||||
@@ -722,6 +470,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
* End this activity by calling finish for activity
|
* End this activity by calling finish for activity
|
||||||
*/
|
*/
|
||||||
public void endActivity() {
|
public void endActivity() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
this.activityState = ACTIVITY_EXITING;
|
this.activityState = ACTIVITY_EXITING;
|
||||||
super.finish();
|
super.finish();
|
||||||
}
|
}
|
||||||
@@ -762,21 +515,16 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
LOG.d(TAG, "Incoming Result");
|
LOG.d(TAG, "Incoming Result");
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
super.onActivityResult(requestCode, resultCode, intent);
|
||||||
Log.d(TAG, "Request code = " + requestCode);
|
Log.d(TAG, "Request code = " + requestCode);
|
||||||
if (appView != null && requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
|
if (appView != null && requestCode == AndroidChromeClient.FILECHOOSER_RESULTCODE) {
|
||||||
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
|
|
||||||
Log.d(TAG, "did we get here?");
|
|
||||||
if (null == mUploadMessage)
|
|
||||||
return;
|
|
||||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||||
Log.d(TAG, "result = " + result);
|
appView.onFilePickerResult(result);
|
||||||
mUploadMessage.onReceiveValue(result);
|
|
||||||
mUploadMessage = null;
|
|
||||||
}
|
}
|
||||||
CordovaPlugin callback = this.activityResultCallback;
|
CordovaPlugin callback = this.activityResultCallback;
|
||||||
if(callback == null && initCallbackClass != null) {
|
if(callback == null && initCallbackClass != null) {
|
||||||
// The application was restarted, but had defined an initial callback
|
// The application was restarted, but had defined an initial callback
|
||||||
// before being shut down.
|
// before being shut down.
|
||||||
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
//this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
||||||
|
this.activityResultCallback = appView.getPluginManager().getPlugin(initCallbackClass);
|
||||||
callback = this.activityResultCallback;
|
callback = this.activityResultCallback;
|
||||||
}
|
}
|
||||||
if(callback != null) {
|
if(callback != null) {
|
||||||
@@ -802,8 +550,11 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
|
|
||||||
// If errorUrl specified, then load it
|
// If errorUrl specified, then load it
|
||||||
final String errorUrl = preferences.getString("errorUrl", null);
|
final String errorUrl = preferences.getString("errorUrl", null);
|
||||||
if ((errorUrl != null) && (errorUrl.startsWith("file://") || internalWhitelist.isUrlWhiteListed(errorUrl)) && (!failingUrl.equals(errorUrl))) {
|
CordovaUriHelper helper = new CordovaUriHelper(this, appView);
|
||||||
|
if ((errorUrl != null) &&
|
||||||
|
(!failingUrl.equals(errorUrl)) &&
|
||||||
|
(appView != null && helper.shouldAllowNavigation(errorUrl))
|
||||||
|
) {
|
||||||
// Load URL on UI thread
|
// Load URL on UI thread
|
||||||
me.runOnUiThread(new Runnable() {
|
me.runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -819,7 +570,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
me.runOnUiThread(new Runnable() {
|
me.runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (exit) {
|
if (exit) {
|
||||||
me.appView.setVisibility(View.GONE);
|
me.appView.getView().setVisibility(View.GONE);
|
||||||
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -857,59 +608,31 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if URL is in approved list of URLs to load.
|
|
||||||
*/
|
|
||||||
@Deprecated // Use whitelist object directly.
|
|
||||||
public boolean isUrlWhiteListed(String url) {
|
|
||||||
return internalWhitelist.isUrlWhiteListed(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hook in Cordova for menu plugins
|
* Hook in Cordova for menu plugins
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
this.postMessage("onCreateOptionsMenu", menu);
|
if (appView != null) {
|
||||||
|
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
||||||
|
}
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
this.postMessage("onPrepareOptionsMenu", menu);
|
if (appView != null) {
|
||||||
|
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
this.postMessage("onOptionsItemSelected", item);
|
if (appView != null) {
|
||||||
return true;
|
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Activity context.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public Context getContext() {
|
|
||||||
LOG.d(TAG, "This will be deprecated December 2012");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
|
||||||
*
|
|
||||||
* NOTE: If openExternal is false, only URLs listed in whitelist 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
|
|
||||||
*/
|
|
||||||
@Deprecated // Call method on appView directly.
|
|
||||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
|
|
||||||
if (this.appView != null) {
|
|
||||||
appView.showWebPage(url, openExternal, clearHistory, params);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Dialog splashDialog;
|
protected Dialog splashDialog;
|
||||||
@@ -970,36 +693,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
this.runOnUiThread(runnable);
|
this.runOnUiThread(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
if (appView != null && (appView.isCustomViewShowing() || appView.getFocusedChild() != null ) &&
|
|
||||||
(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
|
|
||||||
return appView.onKeyUp(keyCode, event);
|
|
||||||
} else {
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Android 2.x needs to be able to check where the cursor is. Android 4.x does not
|
|
||||||
*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
//Determine if the focus is on the current view or not
|
|
||||||
if (appView != null && appView.getFocusedChild() != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
|
|
||||||
return appView.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a message is sent to plugin.
|
* Called when a message is sent to plugin.
|
||||||
*
|
*
|
||||||
@@ -1023,14 +716,14 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
if (splashResource != null) {
|
if (splashResource != null) {
|
||||||
splashscreen = getResources().getIdentifier(splashResource, "drawable", getClass().getPackage().getName());
|
splashscreen = getResources().getIdentifier(splashResource, "drawable", getClass().getPackage().getName());
|
||||||
}
|
}
|
||||||
this.showSplashScreen(this.splashscreenTime);
|
this.showSplashScreen(preferences.getInteger("SplashScreenDelay", 3000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("spinner".equals(id)) {
|
else if ("spinner".equals(id)) {
|
||||||
if ("stop".equals(data.toString())) {
|
if ("stop".equals(data.toString())) {
|
||||||
this.spinnerStop();
|
this.spinnerStop();
|
||||||
this.appView.setVisibility(View.VISIBLE);
|
this.appView.getView().setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("onReceivedError".equals(id)) {
|
else if ("onReceivedError".equals(id)) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
import org.apache.cordova.PluginManager;
|
import org.apache.cordova.PluginManager;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -35,12 +37,14 @@ public class CordovaBridge {
|
|||||||
private NativeToJsMessageQueue jsMessageQueue;
|
private NativeToJsMessageQueue jsMessageQueue;
|
||||||
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||||
private String loadedUrl;
|
private String loadedUrl;
|
||||||
|
protected CordovaUriHelper helper;
|
||||||
|
|
||||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, CordovaUriHelper helper) {
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.jsMessageQueue = jsMessageQueue;
|
this.jsMessageQueue = jsMessageQueue;
|
||||||
|
this.helper = helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||||
if (!verifySecret("exec()", bridgeSecret)) {
|
if (!verifySecret("exec()", bridgeSecret)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -107,7 +111,8 @@ public class CordovaBridge {
|
|||||||
|
|
||||||
/** Called by cordova.js to initialize the bridge. */
|
/** Called by cordova.js to initialize the bridge. */
|
||||||
int generateBridgeSecret() {
|
int generateBridgeSecret() {
|
||||||
expectedBridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
|
SecureRandom randGen = new SecureRandom();
|
||||||
|
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
|
||||||
return expectedBridgeSecret;
|
return expectedBridgeSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,9 +165,9 @@ public class CordovaBridge {
|
|||||||
}
|
}
|
||||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||||
// Protect against random iframes being able to talk through the bridge.
|
// Protect against random iframes being able to talk through the bridge.
|
||||||
// Trust only file URLs and the start URL's domain.
|
// Trust only file URLs and pages which the app would have been allowed
|
||||||
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
|
// to navigate to anyway.
|
||||||
if (origin.startsWith("file:") || (origin.startsWith("http") && loadedUrl.startsWith(origin))) {
|
if (origin.startsWith("file:") || helper.shouldAllowNavigation(origin)) {
|
||||||
// Enable the bridge
|
// Enable the bridge
|
||||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||||
jsMessageQueue.setBridgeMode(bridgeMode);
|
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import android.net.Uri;
|
|||||||
* Plugins must extend this class and override one of the execute methods.
|
* Plugins must extend this class and override one of the execute methods.
|
||||||
*/
|
*/
|
||||||
public class CordovaPlugin {
|
public class CordovaPlugin {
|
||||||
@Deprecated // This is never set.
|
|
||||||
public String id;
|
|
||||||
public CordovaWebView webView;
|
public CordovaWebView webView;
|
||||||
public CordovaInterface cordova;
|
public CordovaInterface cordova;
|
||||||
protected CordovaPreferences preferences;
|
protected CordovaPreferences preferences;
|
||||||
@@ -164,19 +162,67 @@ public class CordovaPlugin {
|
|||||||
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
* 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.
|
* the resultCode it returned, and any additional data from it.
|
||||||
*
|
*
|
||||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||||
* allowing you to identify who this result came from.
|
* allowing you to identify who this result came from.
|
||||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
* @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").
|
* @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) {
|
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 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
|
* By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
|
||||||
*
|
*
|
||||||
* @param url The URL that is trying to be loaded in the Cordova webview.
|
* @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.
|
* @return Return true to prevent the URL from loading. Default is false.
|
||||||
*/
|
*/
|
||||||
public boolean onOverrideUrlLoading(String url) {
|
public boolean onOverrideUrlLoading(String url) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public class CordovaResourceApi {
|
|||||||
// Creating this is light-weight.
|
// Creating this is light-weight.
|
||||||
private static OkHttpClient httpClient = new OkHttpClient();
|
private static OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
|
||||||
static Thread jsThread;
|
public static Thread jsThread;
|
||||||
|
|
||||||
private final AssetManager assetManager;
|
private final AssetManager assetManager;
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
|||||||
@@ -23,49 +23,108 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
class CordovaUriHelper {
|
public class CordovaUriHelper {
|
||||||
|
|
||||||
private static final String TAG = "CordovaUriHelper";
|
private static final String TAG = "CordovaUriHelper";
|
||||||
|
|
||||||
private CordovaWebView appView;
|
private CordovaWebView appView;
|
||||||
private CordovaInterface cordova;
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
public CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
||||||
{
|
{
|
||||||
appView = webView;
|
appView = webView;
|
||||||
cordova = cdv;
|
cordova = cdv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the webview should be allowed to navigate to a given URL.
|
||||||
|
*
|
||||||
|
* This method implements the default whitelist policy when no plugins override
|
||||||
|
* shouldAllowNavigation
|
||||||
|
*/
|
||||||
|
public boolean shouldAllowNavigation(String url) {
|
||||||
|
Boolean pluginManagerAllowsNavigation = this.appView.getPluginManager().shouldAllowNavigation(url);
|
||||||
|
if (pluginManagerAllowsNavigation == null) {
|
||||||
|
// Default policy:
|
||||||
|
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
|
||||||
|
if(url.startsWith("file://") || url.startsWith("data:"))
|
||||||
|
{
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
return pluginManagerAllowsNavigation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the webview should be allowed to launch an intent for a given URL.
|
||||||
|
*
|
||||||
|
* This method implements the default whitelist policy when no plugins override
|
||||||
|
* shouldOpenExternalUrl
|
||||||
|
*/
|
||||||
|
public boolean shouldOpenExternalUrl(String url) {
|
||||||
|
Boolean pluginManagerAllowsExternalUrl = this.appView.getPluginManager().shouldOpenExternalUrl(url);
|
||||||
|
if (pluginManagerAllowsExternalUrl == null) {
|
||||||
|
// Default policy:
|
||||||
|
// External URLs are not allowed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pluginManagerAllowsExternalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the webview should be allowed to request a resource from a given URL.
|
||||||
|
*
|
||||||
|
* This method implements the default whitelist policy when no plugins override
|
||||||
|
* shouldAllowRequest
|
||||||
|
*/
|
||||||
|
public boolean shouldAllowRequest(String url) {
|
||||||
|
|
||||||
|
Boolean pluginManagerAllowsRequest = this.appView.getPluginManager().shouldAllowRequest(url);
|
||||||
|
if (pluginManagerAllowsRequest == null) {
|
||||||
|
// Default policy:
|
||||||
|
// Internal urls on file:// or data:// that do not contain "/app_webview/" are allowed for navigation
|
||||||
|
if(url.startsWith("file://") || url.startsWith("data:"))
|
||||||
|
{
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
return pluginManagerAllowsRequest;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give the host application a chance to take over the control when a new url
|
* Give the host application a chance to take over the control when a new url
|
||||||
* is about to be loaded in the current WebView.
|
* is about to be loaded in the current WebView.
|
||||||
*
|
*
|
||||||
|
* This method implements the default whitelist policy when no plugins override
|
||||||
|
* the whitelist methods:
|
||||||
|
* Internal urls on file:// or data:// that do not contain "app_webview" are allowed for navigation
|
||||||
|
* External urls are not allowed.
|
||||||
|
*
|
||||||
* @param view The WebView that is initiating the callback.
|
* @param view The WebView that is initiating the callback.
|
||||||
* @param url The url to be loaded.
|
* @param url The url to be loaded.
|
||||||
* @return true to override, false for default behavior
|
* @return true to override, false for default behavior
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||||
boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(String url) {
|
||||||
// Give plugins the chance to handle the url
|
// Give plugins the chance to handle the url
|
||||||
if (this.appView.pluginManager.onOverrideUrlLoading(url)) {
|
if (shouldAllowNavigation(url)) {
|
||||||
// Do nothing other than what the plugins wanted.
|
|
||||||
// If any returned true, then the request was handled.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(url.startsWith("file://") | url.startsWith("data:"))
|
|
||||||
{
|
|
||||||
//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");
|
|
||||||
}
|
|
||||||
else if (appView.getWhitelist().isUrlWhiteListed(url)) {
|
|
||||||
// Allow internal navigation
|
// Allow internal navigation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (appView.getExternalWhitelist().isUrlWhiteListed(url))
|
if (shouldOpenExternalUrl(url)) {
|
||||||
{
|
// Do nothing other than what the plugins wanted.
|
||||||
|
// If any returned false, then the request was either blocked
|
||||||
|
// completely, or handled out-of-band by the plugin. If they all
|
||||||
|
// returned true, then we should open the URL here.
|
||||||
try {
|
try {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(url));
|
intent.setData(Uri.parse(url));
|
||||||
@@ -77,10 +136,11 @@ class CordovaUriHelper {
|
|||||||
this.cordova.getActivity().startActivity(intent);
|
this.cordova.getActivity().startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
LOG.e(TAG, "Error loading url " + url, e);
|
Log.e(TAG, "Error loading url " + url, e);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// Intercept the request and do nothing with it -- block it
|
// Block by default
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
913
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
913
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
@@ -1,488 +1,47 @@
|
|||||||
/*
|
|
||||||
Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
or more contributor license agreements. See the NOTICE file
|
|
||||||
distributed with this work for additional information
|
|
||||||
regarding copyright ownership. The ASF licenses this file
|
|
||||||
to you under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance
|
|
||||||
with the License. You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing,
|
|
||||||
software distributed under the License is distributed on an
|
|
||||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
KIND, either express or implied. See the License for the
|
|
||||||
specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.webkit.WebBackForwardList;
|
|
||||||
import android.webkit.WebHistoryItem;
|
|
||||||
import android.webkit.WebChromeClient;
|
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
/*
|
public interface CordovaWebView {
|
||||||
* This class is our web view.
|
public static final String CORDOVA_VERSION = "4.0.0-dev";
|
||||||
*
|
|
||||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
|
||||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
|
|
||||||
*/
|
|
||||||
public class CordovaWebView extends WebView {
|
|
||||||
|
|
||||||
public static final String TAG = "CordovaWebView";
|
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
||||||
public static final String CORDOVA_VERSION = "3.6.4";
|
CordovaPreferences preferences);
|
||||||
|
|
||||||
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
|
View getView();
|
||||||
|
|
||||||
public PluginManager pluginManager;
|
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||||
private boolean paused;
|
|
||||||
|
|
||||||
private BroadcastReceiver receiver;
|
void stopLoading();
|
||||||
|
|
||||||
|
boolean canGoBack();
|
||||||
|
|
||||||
/** Activities and other important classes **/
|
void clearCache(boolean b);
|
||||||
private CordovaInterface cordova;
|
|
||||||
CordovaWebViewClient viewClient;
|
|
||||||
private CordovaChromeClient chromeClient;
|
|
||||||
|
|
||||||
// Flag to track that a loadUrl timeout occurred
|
void clearHistory();
|
||||||
int loadUrlTimeout = 0;
|
|
||||||
|
|
||||||
private long lastMenuEventTime = 0;
|
boolean backHistory();
|
||||||
|
|
||||||
CordovaBridge bridge;
|
void handlePause(boolean keepRunning);
|
||||||
|
|
||||||
/** custom view created by the browser (a video player for example) */
|
void onNewIntent(Intent intent);
|
||||||
private View mCustomView;
|
|
||||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
|
||||||
|
|
||||||
private CordovaResourceApi resourceApi;
|
void handleResume(boolean keepRunning, boolean activityResultKeepRunning);
|
||||||
private Whitelist internalWhitelist;
|
|
||||||
private Whitelist externalWhitelist;
|
|
||||||
|
|
||||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
void handleDestroy();
|
||||||
String loadedUrl;
|
|
||||||
private CordovaPreferences preferences;
|
|
||||||
|
|
||||||
class ActivityResult {
|
|
||||||
|
|
||||||
int request;
|
|
||||||
int result;
|
|
||||||
Intent incoming;
|
|
||||||
|
|
||||||
public ActivityResult(int req, int res, Intent intent) {
|
|
||||||
request = req;
|
|
||||||
result = res;
|
|
||||||
incoming = intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
|
|
||||||
new FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
Gravity.CENTER);
|
|
||||||
|
|
||||||
public CordovaWebView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaWebView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(11)
|
|
||||||
@Deprecated
|
|
||||||
public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
|
|
||||||
super(context, attrs, defStyle, privateBrowsing);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use two-phase init so that the control will work with XML layouts.
|
|
||||||
public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,
|
|
||||||
List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,
|
|
||||||
CordovaPreferences preferences) {
|
|
||||||
if (this.cordova != null) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
this.cordova = cordova;
|
|
||||||
this.viewClient = webViewClient;
|
|
||||||
this.chromeClient = webChromeClient;
|
|
||||||
this.internalWhitelist = internalWhitelist;
|
|
||||||
this.externalWhitelist = externalWhitelist;
|
|
||||||
this.preferences = preferences;
|
|
||||||
super.setWebChromeClient(webChromeClient);
|
|
||||||
super.setWebViewClient(webViewClient);
|
|
||||||
|
|
||||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
|
||||||
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
|
|
||||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
|
||||||
|
|
||||||
pluginManager.addService("App", "org.apache.cordova.App");
|
|
||||||
initWebViewSettings();
|
|
||||||
exposeJsInterface();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private void initIfNecessary() {
|
|
||||||
if (pluginManager == null) {
|
|
||||||
Log.w(TAG, "CordovaWebView.init() was not called. This will soon be required.");
|
|
||||||
// Before the refactor to a two-phase init, the Context needed to implement CordovaInterface.
|
|
||||||
CordovaInterface cdv = (CordovaInterface)getContext();
|
|
||||||
if (!Config.isInitialized()) {
|
|
||||||
Config.init(cdv.getActivity());
|
|
||||||
}
|
|
||||||
init(cdv, makeWebViewClient(cdv), makeWebChromeClient(cdv), Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private void initWebViewSettings() {
|
|
||||||
this.setInitialScale(0);
|
|
||||||
this.setVerticalScrollBarEnabled(false);
|
|
||||||
// TODO: The Activity is the one that should call requestFocus().
|
|
||||||
if (shouldRequestFocusOnInit()) {
|
|
||||||
this.requestFocusFromTouch();
|
|
||||||
}
|
|
||||||
// Enable JavaScript
|
|
||||||
WebSettings settings = this.getSettings();
|
|
||||||
settings.setJavaScriptEnabled(true);
|
|
||||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
|
||||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
|
||||||
|
|
||||||
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
|
|
||||||
try {
|
|
||||||
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
|
|
||||||
|
|
||||||
String manufacturer = android.os.Build.MANUFACTURER;
|
|
||||||
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
|
||||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
|
||||||
android.os.Build.MANUFACTURER.contains("HTC"))
|
|
||||||
{
|
|
||||||
gingerbread_getMethod.invoke(settings, true);
|
|
||||||
}
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.d(TAG, "Doing the NavDump failed with bad arguments");
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
|
|
||||||
}
|
|
||||||
|
|
||||||
//We don't save any form data in the application
|
|
||||||
settings.setSaveFormData(false);
|
|
||||||
settings.setSavePassword(false);
|
|
||||||
|
|
||||||
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
|
|
||||||
// while we do this
|
|
||||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
|
||||||
Level16Apis.enableUniversalAccess(settings);
|
|
||||||
// Enable database
|
|
||||||
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
|
||||||
String databasePath = 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 = getContext().getApplicationContext().getApplicationInfo();
|
|
||||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
|
|
||||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
|
||||||
enableRemoteDebugging();
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.setGeolocationDatabasePath(databasePath);
|
|
||||||
|
|
||||||
// Enable DOM storage
|
|
||||||
settings.setDomStorageEnabled(true);
|
|
||||||
|
|
||||||
// Enable built-in geolocation
|
|
||||||
settings.setGeolocationEnabled(true);
|
|
||||||
|
|
||||||
// Enable AppCache
|
|
||||||
// Fix for CB-2282
|
|
||||||
settings.setAppCacheMaxSize(5 * 1048576);
|
|
||||||
settings.setAppCachePath(databasePath);
|
|
||||||
settings.setAppCacheEnabled(true);
|
|
||||||
|
|
||||||
// Fix for CB-1405
|
|
||||||
// Google issue 4641
|
|
||||||
settings.getUserAgentString();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
getSettings().getUserAgentString();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getContext().registerReceiver(this.receiver, intentFilter);
|
|
||||||
}
|
|
||||||
// end CB-1405
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
private void enableRemoteDebugging() {
|
|
||||||
try {
|
|
||||||
WebView.setWebContentsDebuggingEnabled(true);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaChromeClient makeWebChromeClient(CordovaInterface cordova) {
|
|
||||||
return new CordovaChromeClient(cordova, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaWebViewClient makeWebViewClient(CordovaInterface cordova) {
|
|
||||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
|
||||||
return new CordovaWebViewClient(cordova, this);
|
|
||||||
}
|
|
||||||
return new IceCreamCordovaWebViewClient(cordova, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method to decide whether or not you need to request the
|
|
||||||
* focus when your application start
|
|
||||||
*
|
|
||||||
* @return true unless this method is overriden to return a different value
|
|
||||||
*/
|
|
||||||
protected boolean shouldRequestFocusOnInit() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exposeJsInterface() {
|
|
||||||
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
|
|
||||||
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
|
|
||||||
// Bug being that Java Strings do not get converted to JS strings automatically.
|
|
||||||
// This isn't hard to work-around on the JS side, but it's easier to just
|
|
||||||
// use the prompt bridge instead.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.addJavascriptInterface(new ExposedJsApi(bridge), "_cordovaNative");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWebViewClient(WebViewClient client) {
|
|
||||||
this.viewClient = (CordovaWebViewClient)client;
|
|
||||||
super.setWebViewClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWebChromeClient(WebChromeClient client) {
|
|
||||||
this.chromeClient = (CordovaChromeClient)client;
|
|
||||||
super.setWebChromeClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaChromeClient getWebChromeClient() {
|
|
||||||
return this.chromeClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Whitelist getWhitelist() {
|
|
||||||
return this.internalWhitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Whitelist getExternalWhitelist() {
|
|
||||||
return this.externalWhitelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void loadUrl(String url) {
|
|
||||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
|
||||||
this.loadUrlNow(url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.loadUrlIntoView(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview after waiting for period of time.
|
|
||||||
* This is used to display the splashscreen for certain amount of time.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param time The number of ms to wait before loading webview
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void loadUrl(final String url, int time) {
|
|
||||||
if(url == null)
|
|
||||||
{
|
|
||||||
this.loadUrlIntoView(Config.getStartUrl());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.loadUrlIntoView(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadUrlIntoView(final String url) {
|
|
||||||
loadUrlIntoView(url, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
|
||||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
|
||||||
|
|
||||||
initIfNecessary();
|
|
||||||
|
|
||||||
if (recreatePlugins) {
|
|
||||||
this.loadedUrl = url;
|
|
||||||
this.pluginManager.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a timeout timer for loadUrl
|
|
||||||
final CordovaWebView me = this;
|
|
||||||
final int currentLoadUrlTimeout = me.loadUrlTimeout;
|
|
||||||
final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("LoadUrlTimeoutValue", "20000"));
|
|
||||||
|
|
||||||
// Timeout error method
|
|
||||||
final Runnable loadError = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
me.stopLoading();
|
|
||||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
|
||||||
if (viewClient != null) {
|
|
||||||
viewClient.onReceivedError(me, -6, "The connection to the server was unsuccessful.", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 (me.loadUrlTimeout == currentLoadUrlTimeout) {
|
|
||||||
me.cordova.getActivity().runOnUiThread(loadError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load url
|
|
||||||
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
cordova.getThreadPool().execute(timeoutCheck);
|
|
||||||
me.loadUrlNow(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load URL in webview.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
void loadUrlNow(String url) {
|
|
||||||
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
|
|
||||||
LOG.d(TAG, ">>> loadUrlNow()");
|
|
||||||
}
|
|
||||||
if (url.startsWith("file://") || url.startsWith("javascript:") || internalWhitelist.isUrlWhiteListed(url)) {
|
|
||||||
super.loadUrl(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview after waiting for period of time.
|
|
||||||
* This is used to display the splashscreen for certain amount of time.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param time The number of ms to wait before loading webview
|
|
||||||
*/
|
|
||||||
public void loadUrlIntoView(final String url, final int time) {
|
|
||||||
|
|
||||||
// If not first page of app, then load immediately
|
|
||||||
// Add support for browser history if we use it.
|
|
||||||
if ((url.startsWith("javascript:")) || this.canGoBack()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// If first page, then show splashscreen
|
|
||||||
else {
|
|
||||||
|
|
||||||
LOG.d(TAG, "loadUrlIntoView(%s, %d)", url, time);
|
|
||||||
|
|
||||||
// Send message to show splashscreen now if desired
|
|
||||||
this.postMessage("splashscreen", "show");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load url
|
|
||||||
this.loadUrlIntoView(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopLoading() {
|
|
||||||
viewClient.isCurrentlyLoading = false;
|
|
||||||
super.stopLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onScrollChanged(int l, int t, int oldl, int oldt)
|
|
||||||
{
|
|
||||||
super.onScrollChanged(l, t, oldl, oldt);
|
|
||||||
//We should post a message that the scroll changed
|
|
||||||
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
|
|
||||||
this.postMessage("onScrollChanged", myEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send JavaScript statement back to JavaScript.
|
* Send JavaScript statement back to JavaScript.
|
||||||
|
* (This is a convenience method)
|
||||||
|
*
|
||||||
|
* @param statement
|
||||||
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||||
* Instead of executing snippets of JS, you should use the exec bridge
|
* Instead of executing snippets of JS, you should use the exec bridge
|
||||||
* to create a Java->JS communication channel.
|
* to create a Java->JS communication channel.
|
||||||
@@ -502,433 +61,35 @@ public class CordovaWebView extends WebView {
|
|||||||
* savedCallbackContext.sendPluginResult(dataResult);
|
* savedCallbackContext.sendPluginResult(dataResult);
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void sendJavascript(String statement) {
|
void sendJavascript(String statememt);
|
||||||
this.bridge.getMessageQueue().addJavaScript(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
void showWebPage(String errorUrl, boolean b, boolean c, HashMap<String, Object> params);
|
||||||
* Send a plugin result back to JavaScript.
|
|
||||||
* (This is a convenience method)
|
|
||||||
*
|
|
||||||
* @param result
|
|
||||||
* @param callbackId
|
|
||||||
*/
|
|
||||||
public void sendPluginResult(PluginResult result, String callbackId) {
|
|
||||||
this.bridge.getMessageQueue().addPluginResult(result, callbackId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
boolean isCustomViewShowing();
|
||||||
* Send a message to all plugins.
|
|
||||||
*
|
|
||||||
* @param id The message id
|
|
||||||
* @param data The message data
|
|
||||||
*/
|
|
||||||
public void postMessage(String id, Object data) {
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.postMessage(id, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void showCustomView(View view, CustomViewCallback callback);
|
||||||
|
|
||||||
/**
|
void hideCustomView();
|
||||||
* Go to previous page in history. (We manage our own history)
|
|
||||||
*
|
|
||||||
* @return true if we went back, false if we are already at top
|
|
||||||
*/
|
|
||||||
public boolean backHistory() {
|
|
||||||
// Check webview first to see if there is a history
|
|
||||||
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
|
|
||||||
if (super.canGoBack()) {
|
|
||||||
super.goBack();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CordovaResourceApi getResourceApi();
|
||||||
|
|
||||||
/**
|
void setButtonPlumbedToJs(int keyCode, boolean override);
|
||||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
boolean isButtonPlumbedToJs(int keyCode);
|
||||||
*
|
|
||||||
* NOTE: If openExternal is false, only URLs listed in whitelist 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
|
|
||||||
*/
|
|
||||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
|
|
||||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
|
|
||||||
|
|
||||||
// If clearing history
|
void sendPluginResult(PluginResult cr, String callbackId);
|
||||||
if (clearHistory) {
|
|
||||||
this.clearHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If loading into our webview
|
PluginManager getPluginManager();
|
||||||
if (!openExternal) {
|
|
||||||
|
|
||||||
// Make sure url is in whitelist
|
CordovaPreferences getPreferences();
|
||||||
if (url.startsWith("file://") || internalWhitelist.isUrlWhiteListed(url)) {
|
|
||||||
// TODO: What about params?
|
|
||||||
// Load new URL
|
|
||||||
this.loadUrl(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Load in default viewer if not
|
|
||||||
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// 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.
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
Uri uri = Uri.parse(url);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get string property for activity.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param defaultValue
|
|
||||||
* @return the String value for the named property
|
|
||||||
*/
|
|
||||||
public String getProperty(String name, String defaultValue) {
|
|
||||||
Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
|
|
||||||
if (bundle == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
name = name.toLowerCase(Locale.getDefault());
|
|
||||||
Object p = bundle.get(name);
|
|
||||||
if (p == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return p.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
if(boundKeyCodes.contains(keyCode))
|
|
||||||
{
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
|
||||||
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If volumeup key
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
|
||||||
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(keyCode == KeyEvent.KEYCODE_BACK)
|
|
||||||
{
|
|
||||||
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
|
||||||
}
|
|
||||||
else if(keyCode == KeyEvent.KEYCODE_MENU)
|
|
||||||
{
|
|
||||||
//How did we get here? Is there a childView?
|
|
||||||
View childView = this.getFocusedChild();
|
|
||||||
if(childView != null)
|
|
||||||
{
|
|
||||||
//Make sure we close the keyboard if it's present
|
|
||||||
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
|
|
||||||
cordova.getActivity().openOptionsMenu();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
// If back key
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
// A custom view is currently displayed (e.g. playing a video)
|
|
||||||
if(mCustomView != null) {
|
|
||||||
this.hideCustomView();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// The webview is currently displayed
|
|
||||||
// If back key is bound, then send event to JavaScript
|
|
||||||
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
|
|
||||||
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// If not bound
|
|
||||||
// Go to previous page in webview if it is possible to go back
|
|
||||||
if (this.backHistory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If not, then invoke default behavior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Legacy
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
||||||
if (this.lastMenuEventTime < event.getEventTime()) {
|
|
||||||
this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
|
|
||||||
}
|
|
||||||
this.lastMenuEventTime = event.getEventTime();
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
// If search key
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
|
||||||
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Does webkit change this behavior?
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
|
||||||
switch (keyCode) {
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
|
||||||
case KeyEvent.KEYCODE_BACK:
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
|
||||||
public void bindButton(boolean override)
|
|
||||||
{
|
|
||||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
|
||||||
public void bindButton(String button, boolean override) {
|
|
||||||
if (button.compareTo("volumeup")==0) {
|
|
||||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
|
|
||||||
}
|
|
||||||
else if (button.compareTo("volumedown")==0) {
|
|
||||||
setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // Use setButtonPlumbedToJs() instead.
|
|
||||||
public void bindButton(int keyCode, boolean keyDown, boolean override) {
|
|
||||||
setButtonPlumbedToJs(keyCode, override);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // Use isButtonPlumbedToJs
|
|
||||||
public boolean isBackButtonBound()
|
|
||||||
{
|
|
||||||
return isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isButtonPlumbedToJs(int keyCode)
|
|
||||||
{
|
|
||||||
return boundKeyCodes.contains(keyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handlePause(boolean keepRunning)
|
|
||||||
{
|
|
||||||
LOG.d(TAG, "Handle the pause");
|
|
||||||
// Send pause event to JavaScript
|
|
||||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
|
|
||||||
|
|
||||||
// Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onPause(keepRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If app doesn't want to run in background
|
|
||||||
if (!keepRunning) {
|
|
||||||
// Pause JavaScript timers (including setInterval)
|
|
||||||
this.pauseTimers();
|
|
||||||
}
|
|
||||||
paused = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
|
void onFilePickerResult(Uri uri);
|
||||||
{
|
|
||||||
|
|
||||||
this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
|
void setNetworkAvailable(boolean online);
|
||||||
|
|
||||||
// Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onResume(keepRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume JavaScript timers (including setInterval)
|
|
||||||
this.resumeTimers();
|
|
||||||
paused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleDestroy()
|
String getUrl();
|
||||||
{
|
|
||||||
// Send destroy event to JavaScript
|
|
||||||
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
|
|
||||||
|
|
||||||
// Load blank page so that JavaScript onunload is called
|
// TODO: Work on deleting these by removing refs from plugins.
|
||||||
this.loadUrl("about:blank");
|
Context getContext();
|
||||||
|
void loadUrl(String url);
|
||||||
// Forward to plugins
|
Object postMessage(String id, Object data);
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregister the receiver
|
|
||||||
if (this.receiver != null) {
|
|
||||||
try {
|
|
||||||
getContext().unregisterReceiver(this.receiver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNewIntent(Intent intent)
|
|
||||||
{
|
|
||||||
//Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onNewIntent(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPaused()
|
|
||||||
{
|
|
||||||
return paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // This never did anything.
|
|
||||||
public boolean hadKeyEvent() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapping these functions in their own class prevents warnings in adb like:
|
|
||||||
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
|
|
||||||
@TargetApi(16)
|
|
||||||
private static class Level16Apis {
|
|
||||||
static void enableUniversalAccess(WebSettings settings) {
|
|
||||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void printBackForwardList() {
|
|
||||||
WebBackForwardList currentList = this.copyBackForwardList();
|
|
||||||
int currentSize = currentList.getSize();
|
|
||||||
for(int i = 0; i < currentSize; ++i)
|
|
||||||
{
|
|
||||||
WebHistoryItem item = currentList.getItemAtIndex(i);
|
|
||||||
String url = item.getUrl();
|
|
||||||
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Can Go Back is BROKEN!
|
|
||||||
public boolean startOfHistory()
|
|
||||||
{
|
|
||||||
WebBackForwardList currentList = this.copyBackForwardList();
|
|
||||||
WebHistoryItem item = currentList.getItemAtIndex(0);
|
|
||||||
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
|
|
||||||
String url = item.getUrl();
|
|
||||||
String currentUrl = this.getUrl();
|
|
||||||
LOG.d(TAG, "The current URL is: " + currentUrl);
|
|
||||||
LOG.d(TAG, "The URL at item 0 is: " + url);
|
|
||||||
return currentUrl.equals(url);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) this.getParent();
|
|
||||||
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
|
|
||||||
|
|
||||||
// Hide the content view.
|
|
||||||
this.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Finally show the custom view container.
|
|
||||||
parent.setVisibility(View.VISIBLE);
|
|
||||||
parent.bringToFront();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideCustomView() {
|
|
||||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
|
||||||
Log.d(TAG, "Hiding Custom View");
|
|
||||||
if (mCustomView == null) return;
|
|
||||||
|
|
||||||
// Hide the custom view.
|
|
||||||
mCustomView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Remove the custom view from its container.
|
|
||||||
ViewGroup parent = (ViewGroup) this.getParent();
|
|
||||||
parent.removeView(mCustomView);
|
|
||||||
mCustomView = null;
|
|
||||||
mCustomViewCallback.onCustomViewHidden();
|
|
||||||
|
|
||||||
// Show the content view.
|
|
||||||
this.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if the video overlay is showing then we need to know
|
|
||||||
* as it effects back button handling
|
|
||||||
*
|
|
||||||
* @return true if custom view is showing
|
|
||||||
*/
|
|
||||||
public boolean isCustomViewShowing() {
|
|
||||||
return mCustomView != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebBackForwardList restoreState(Bundle savedInstanceState)
|
|
||||||
{
|
|
||||||
WebBackForwardList myList = super.restoreState(savedInstanceState);
|
|
||||||
Log.d(TAG, "WebView restoration crew now restoring!");
|
|
||||||
//Initialize the plugin manager once more
|
|
||||||
this.pluginManager.init();
|
|
||||||
return myList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // This never did anything
|
|
||||||
public void storeResult(int requestCode, int resultCode, Intent intent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaResourceApi getResourceApi() {
|
|
||||||
return resourceApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaPreferences getPreferences() {
|
|
||||||
return preferences;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.HashMap;
|
|||||||
/**
|
/**
|
||||||
* This class exposes methods in Cordova that can be called from JavaScript.
|
* This class exposes methods in Cordova that can be called from JavaScript.
|
||||||
*/
|
*/
|
||||||
public class App extends CordovaPlugin {
|
public class CoreAndroid extends CordovaPlugin {
|
||||||
|
|
||||||
protected static final String TAG = "CordovaApp";
|
protected static final String TAG = "CordovaApp";
|
||||||
private BroadcastReceiver telephonyReceiver;
|
private BroadcastReceiver telephonyReceiver;
|
||||||
@@ -75,7 +75,7 @@ public class App extends CordovaPlugin {
|
|||||||
// indicative of what this actually does (shows the webview).
|
// indicative of what this actually does (shows the webview).
|
||||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
webView.postMessage("spinner", "stop");
|
webView.getPluginManager().postMessage("spinner", "stop");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ public class App extends CordovaPlugin {
|
|||||||
* Exit the Android application.
|
* Exit the Android application.
|
||||||
*/
|
*/
|
||||||
public void exitApp() {
|
public void exitApp() {
|
||||||
this.webView.postMessage("exit", null);
|
this.webView.getPluginManager().postMessage("exit", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -271,15 +271,15 @@ public class App extends CordovaPlugin {
|
|||||||
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
||||||
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
||||||
LOG.i(TAG, "Telephone RINGING");
|
LOG.i(TAG, "Telephone RINGING");
|
||||||
webView.postMessage("telephone", "ringing");
|
webView.getPluginManager().postMessage("telephone", "ringing");
|
||||||
}
|
}
|
||||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
||||||
LOG.i(TAG, "Telephone OFFHOOK");
|
LOG.i(TAG, "Telephone OFFHOOK");
|
||||||
webView.postMessage("telephone", "offhook");
|
webView.getPluginManager().postMessage("telephone", "offhook");
|
||||||
}
|
}
|
||||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
||||||
LOG.i(TAG, "Telephone IDLE");
|
LOG.i(TAG, "Telephone IDLE");
|
||||||
webView.postMessage("telephone", "idle");
|
webView.getPluginManager().postMessage("telephone", "idle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,162 +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.File;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.StatFs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides file directory utilities.
|
|
||||||
* All file operations are performed on the SD card.
|
|
||||||
*
|
|
||||||
* It is used by the FileUtils class.
|
|
||||||
*/
|
|
||||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
|
||||||
public class DirectoryManager {
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String LOG_TAG = "DirectoryManager";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a file or directory exists.
|
|
||||||
* @param name The name of the file to check.
|
|
||||||
* @return T=exists, F=not found
|
|
||||||
*/
|
|
||||||
public static boolean testFileExists(String name) {
|
|
||||||
boolean status;
|
|
||||||
|
|
||||||
// If SD card exists
|
|
||||||
if ((testSaveLocationExists()) && (!name.equals(""))) {
|
|
||||||
File path = Environment.getExternalStorageDirectory();
|
|
||||||
File newPath = constructFilePaths(path.toString(), name);
|
|
||||||
status = newPath.exists();
|
|
||||||
}
|
|
||||||
// If no SD card
|
|
||||||
else {
|
|
||||||
status = false;
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the free disk space
|
|
||||||
*
|
|
||||||
* @return Size in KB or -1 if not available
|
|
||||||
*/
|
|
||||||
public static long getFreeDiskSpace(boolean checkInternal) {
|
|
||||||
String status = Environment.getExternalStorageState();
|
|
||||||
long freeSpace = 0;
|
|
||||||
|
|
||||||
// If SD card exists
|
|
||||||
if (status.equals(Environment.MEDIA_MOUNTED)) {
|
|
||||||
freeSpace = freeSpaceCalculation(Environment.getExternalStorageDirectory().getPath());
|
|
||||||
}
|
|
||||||
else if (checkInternal) {
|
|
||||||
freeSpace = freeSpaceCalculation("/");
|
|
||||||
}
|
|
||||||
// If no SD card and we haven't been asked to check the internal directory then return -1
|
|
||||||
else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return freeSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a path return the number of free KB
|
|
||||||
*
|
|
||||||
* @param path to the file system
|
|
||||||
* @return free space in KB
|
|
||||||
*/
|
|
||||||
private static long freeSpaceCalculation(String path) {
|
|
||||||
StatFs stat = new StatFs(path);
|
|
||||||
long blockSize = stat.getBlockSize();
|
|
||||||
long availableBlocks = stat.getAvailableBlocks();
|
|
||||||
return availableBlocks * blockSize / 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if SD card exists.
|
|
||||||
*
|
|
||||||
* @return T=exists, F=not found
|
|
||||||
*/
|
|
||||||
public static boolean testSaveLocationExists() {
|
|
||||||
String sDCardStatus = Environment.getExternalStorageState();
|
|
||||||
boolean status;
|
|
||||||
|
|
||||||
// If SD card is mounted
|
|
||||||
if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
|
|
||||||
status = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no SD card
|
|
||||||
else {
|
|
||||||
status = false;
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new file object from two file paths.
|
|
||||||
*
|
|
||||||
* @param file1 Base file path
|
|
||||||
* @param file2 Remaining file path
|
|
||||||
* @return File object
|
|
||||||
*/
|
|
||||||
private static File constructFilePaths (String file1, String file2) {
|
|
||||||
File newPath;
|
|
||||||
if (file2.startsWith(file1)) {
|
|
||||||
newPath = new File(file2);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newPath = new File(file1 + "/" + file2);
|
|
||||||
}
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if we can use the SD Card to store the temporary file. If not then use
|
|
||||||
* the internal cache directory.
|
|
||||||
*
|
|
||||||
* @return the absolute path of where to store the file
|
|
||||||
*/
|
|
||||||
public static String getTempDirectoryPath(Context ctx) {
|
|
||||||
File cache = null;
|
|
||||||
|
|
||||||
// SD Card Mounted
|
|
||||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
||||||
cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
|
|
||||||
"/Android/data/" + ctx.getPackageName() + "/cache/");
|
|
||||||
}
|
|
||||||
// Use internal storage
|
|
||||||
else {
|
|
||||||
cache = ctx.getCacheDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the cache directory if it doesn't exist
|
|
||||||
if (!cache.exists()) {
|
|
||||||
cache.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.getAbsolutePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +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 used to be the class that should be extended by application
|
|
||||||
* developers, but everything has been moved to CordovaActivity. So
|
|
||||||
* you should extend CordovaActivity instead of DroidGap. This class
|
|
||||||
* will be removed at a future time.
|
|
||||||
*
|
|
||||||
* @see CordovaActivity
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class DroidGap extends CordovaActivity {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,186 +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 android.media.ExifInterface;
|
|
||||||
|
|
||||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
|
||||||
public class ExifHelper {
|
|
||||||
private String aperture = null;
|
|
||||||
private String datetime = null;
|
|
||||||
private String exposureTime = null;
|
|
||||||
private String flash = null;
|
|
||||||
private String focalLength = null;
|
|
||||||
private String gpsAltitude = null;
|
|
||||||
private String gpsAltitudeRef = null;
|
|
||||||
private String gpsDateStamp = null;
|
|
||||||
private String gpsLatitude = null;
|
|
||||||
private String gpsLatitudeRef = null;
|
|
||||||
private String gpsLongitude = null;
|
|
||||||
private String gpsLongitudeRef = null;
|
|
||||||
private String gpsProcessingMethod = null;
|
|
||||||
private String gpsTimestamp = null;
|
|
||||||
private String iso = null;
|
|
||||||
private String make = null;
|
|
||||||
private String model = null;
|
|
||||||
private String orientation = null;
|
|
||||||
private String whiteBalance = null;
|
|
||||||
|
|
||||||
private ExifInterface inFile = null;
|
|
||||||
private ExifInterface outFile = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file before it is compressed
|
|
||||||
*
|
|
||||||
* @param filePath
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void createInFile(String filePath) throws IOException {
|
|
||||||
this.inFile = new ExifInterface(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file after it has been compressed
|
|
||||||
*
|
|
||||||
* @param filePath
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void createOutFile(String filePath) throws IOException {
|
|
||||||
this.outFile = new ExifInterface(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all the EXIF data from the input file.
|
|
||||||
*/
|
|
||||||
public void readExifData() {
|
|
||||||
this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE);
|
|
||||||
this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME);
|
|
||||||
this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
|
|
||||||
this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH);
|
|
||||||
this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
|
|
||||||
this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE);
|
|
||||||
this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF);
|
|
||||||
this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
|
|
||||||
this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
|
||||||
this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
|
||||||
this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
|
||||||
this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
|
||||||
this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
|
|
||||||
this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
|
|
||||||
this.iso = inFile.getAttribute(ExifInterface.TAG_ISO);
|
|
||||||
this.make = inFile.getAttribute(ExifInterface.TAG_MAKE);
|
|
||||||
this.model = inFile.getAttribute(ExifInterface.TAG_MODEL);
|
|
||||||
this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION);
|
|
||||||
this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the previously stored EXIF data to the output file.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void writeExifData() throws IOException {
|
|
||||||
// Don't try to write to a null file
|
|
||||||
if (this.outFile == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.aperture != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture);
|
|
||||||
}
|
|
||||||
if (this.datetime != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime);
|
|
||||||
}
|
|
||||||
if (this.exposureTime != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime);
|
|
||||||
}
|
|
||||||
if (this.flash != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash);
|
|
||||||
}
|
|
||||||
if (this.focalLength != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength);
|
|
||||||
}
|
|
||||||
if (this.gpsAltitude != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude);
|
|
||||||
}
|
|
||||||
if (this.gpsAltitudeRef != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef);
|
|
||||||
}
|
|
||||||
if (this.gpsDateStamp != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp);
|
|
||||||
}
|
|
||||||
if (this.gpsLatitude != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude);
|
|
||||||
}
|
|
||||||
if (this.gpsLatitudeRef != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef);
|
|
||||||
}
|
|
||||||
if (this.gpsLongitude != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude);
|
|
||||||
}
|
|
||||||
if (this.gpsLongitudeRef != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef);
|
|
||||||
}
|
|
||||||
if (this.gpsProcessingMethod != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod);
|
|
||||||
}
|
|
||||||
if (this.gpsTimestamp != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp);
|
|
||||||
}
|
|
||||||
if (this.iso != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso);
|
|
||||||
}
|
|
||||||
if (this.make != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make);
|
|
||||||
}
|
|
||||||
if (this.model != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model);
|
|
||||||
}
|
|
||||||
if (this.orientation != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation);
|
|
||||||
}
|
|
||||||
if (this.whiteBalance != null) {
|
|
||||||
this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outFile.saveAttributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOrientation() {
|
|
||||||
int o = Integer.parseInt(this.orientation);
|
|
||||||
|
|
||||||
if (o == ExifInterface.ORIENTATION_NORMAL) {
|
|
||||||
return 0;
|
|
||||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
|
|
||||||
return 90;
|
|
||||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
|
|
||||||
return 180;
|
|
||||||
} else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
|
|
||||||
return 270;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetOrientation() {
|
|
||||||
this.orientation = "" + ExifInterface.ORIENTATION_NORMAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
framework/src/org/apache/cordova/ExposedJsApi.java
Executable file → Normal file
52
framework/src/org/apache/cordova/ExposedJsApi.java
Executable file → Normal file
@@ -1,52 +1,12 @@
|
|||||||
/*
|
|
||||||
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;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import android.webkit.JavascriptInterface;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Contains APIs that the JS can call. All functions in here should also have
|
* Any exposed Javascript API MUST implement these three things!
|
||||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
|
||||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
|
||||||
*/
|
*/
|
||||||
/* package */ class ExposedJsApi {
|
public interface ExposedJsApi {
|
||||||
|
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
|
||||||
private CordovaBridge bridge;
|
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
|
||||||
|
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
|
||||||
public ExposedJsApi(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,163 +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.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
|
|
||||||
import org.apache.cordova.CordovaInterface;
|
|
||||||
import org.apache.cordova.LOG;
|
|
||||||
|
|
||||||
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.nio.charset.Charset;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
|
||||||
public class FileHelper {
|
|
||||||
private static final String LOG_TAG = "FileUtils";
|
|
||||||
private static final String _DATA = "_data";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the real path of the given URI string.
|
|
||||||
* If the given URI string represents a content:// URI, the real path is retrieved from the media store.
|
|
||||||
*
|
|
||||||
* @param uriString the URI string of the audio/image/video
|
|
||||||
* @param cordova the current application context
|
|
||||||
* @return the full path to the file
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static String getRealPath(String uriString, CordovaInterface cordova) {
|
|
||||||
String realPath = null;
|
|
||||||
|
|
||||||
if (uriString.startsWith("content://")) {
|
|
||||||
String[] proj = { _DATA };
|
|
||||||
Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
|
|
||||||
int column_index = cursor.getColumnIndexOrThrow(_DATA);
|
|
||||||
cursor.moveToFirst();
|
|
||||||
realPath = cursor.getString(column_index);
|
|
||||||
if (realPath == null) {
|
|
||||||
LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
|
|
||||||
}
|
|
||||||
} else if (uriString.startsWith("file://")) {
|
|
||||||
realPath = uriString.substring(7);
|
|
||||||
if (realPath.startsWith("/android_asset/")) {
|
|
||||||
LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
|
|
||||||
realPath = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
realPath = uriString;
|
|
||||||
}
|
|
||||||
|
|
||||||
return realPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the real path of the given URI.
|
|
||||||
* If the given URI is a content:// URI, the real path is retrieved from the media store.
|
|
||||||
*
|
|
||||||
* @param uri the URI of the audio/image/video
|
|
||||||
* @param cordova the current application context
|
|
||||||
* @return the full path to the file
|
|
||||||
*/
|
|
||||||
public static String getRealPath(Uri uri, CordovaInterface cordova) {
|
|
||||||
return FileHelper.getRealPath(uri.toString(), cordova);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an input stream based on given URI string.
|
|
||||||
*
|
|
||||||
* @param uriString the URI string from which to obtain the input stream
|
|
||||||
* @param cordova the current application context
|
|
||||||
* @return an input stream into the data at the given URI or null if given an invalid URI string
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
|
|
||||||
if (uriString.startsWith("content")) {
|
|
||||||
Uri uri = Uri.parse(uriString);
|
|
||||||
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
|
||||||
} else if (uriString.startsWith("file://")) {
|
|
||||||
int question = uriString.indexOf("?");
|
|
||||||
if (question > -1) {
|
|
||||||
uriString = uriString.substring(0,question);
|
|
||||||
}
|
|
||||||
if (uriString.startsWith("file:///android_asset/")) {
|
|
||||||
Uri uri = Uri.parse(uriString);
|
|
||||||
String relativePath = uri.getPath().substring(15);
|
|
||||||
return cordova.getActivity().getAssets().open(relativePath);
|
|
||||||
} else {
|
|
||||||
return new FileInputStream(getRealPath(uriString, cordova));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new FileInputStream(getRealPath(uriString, cordova));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the "file://" prefix from the given URI string, if applicable.
|
|
||||||
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
|
|
||||||
*
|
|
||||||
* @param uriString the URI string to operate on
|
|
||||||
* @return a path without the "file://" prefix
|
|
||||||
*/
|
|
||||||
public static String stripFileProtocol(String uriString) {
|
|
||||||
if (uriString.startsWith("file://")) {
|
|
||||||
uriString = uriString.substring(7);
|
|
||||||
}
|
|
||||||
return uriString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getMimeTypeForExtension(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";
|
|
||||||
}
|
|
||||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mime type of the data specified by the given URI string.
|
|
||||||
*
|
|
||||||
* @param uriString the URI string of the data
|
|
||||||
* @return the mime type of the specified data
|
|
||||||
*/
|
|
||||||
public static String getMimeType(String uriString, CordovaInterface cordova) {
|
|
||||||
String mimeType = null;
|
|
||||||
|
|
||||||
Uri uri = Uri.parse(uriString);
|
|
||||||
if (uriString.startsWith("content://")) {
|
|
||||||
mimeType = cordova.getActivity().getContentResolver().getType(uri);
|
|
||||||
} else {
|
|
||||||
mimeType = getMimeTypeForExtension(uri.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,16 +32,11 @@ import android.webkit.WebResourceResponse;
|
|||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient {
|
||||||
|
|
||||||
private static final String TAG = "IceCreamCordovaWebViewClient";
|
private static final String TAG = "IceCreamCordovaWebViewClient";
|
||||||
private CordovaUriHelper helper;
|
|
||||||
|
|
||||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
|
public IceCreamCordovaWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
||||||
super(cordova);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
|
||||||
super(cordova, view);
|
super(cordova, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +45,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
try {
|
try {
|
||||||
// Check the against the whitelist and lock out access to the WebView directory
|
// Check the against the whitelist and lock out access to the WebView directory
|
||||||
// Changing this will cause problems for your application
|
// Changing this will cause problems for your application
|
||||||
if (isUrlHarmful(url)) {
|
if (!helper.shouldAllowRequest(url)) {
|
||||||
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||||
// Results in a 404.
|
// Results in a 404.
|
||||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||||
@@ -76,11 +71,6 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUrlHarmful(String url) {
|
|
||||||
return ((url.startsWith("http:") || url.startsWith("https:")) && !appView.getWhitelist().isUrlWhiteListed(url))
|
|
||||||
|| url.contains("app_webview");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
||||||
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
|
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +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.List;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
@Deprecated // Deprecated in 3.1. To be removed in 4.0.
|
|
||||||
public class JSONUtils {
|
|
||||||
public static List<String> toStringList(JSONArray array) throws JSONException {
|
|
||||||
if(array == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
List<String> list = new ArrayList<String>();
|
|
||||||
|
|
||||||
for (int i = 0; i < array.length(); i++) {
|
|
||||||
list.add(array.get(i).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -37,6 +37,7 @@ public class NativeToJsMessageQueue {
|
|||||||
|
|
||||||
// Set this to true to force plugin results to be encoding as
|
// Set this to true to force plugin results to be encoding as
|
||||||
// JS instead of the custom format (useful for benchmarking).
|
// JS instead of the custom format (useful for benchmarking).
|
||||||
|
// Doesn't work for multipart messages.
|
||||||
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
||||||
|
|
||||||
// Disable sending back native->JS messages during an exec() when the active
|
// Disable sending back native->JS messages during an exec() when the active
|
||||||
@@ -84,7 +85,7 @@ public class NativeToJsMessageQueue {
|
|||||||
registeredListeners[3] = new PrivateApiBridgeMode();
|
registeredListeners[3] = new PrivateApiBridgeMode();
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBridgeEnabled() {
|
public boolean isBridgeEnabled() {
|
||||||
return activeBridgeMode != null;
|
return activeBridgeMode != null;
|
||||||
}
|
}
|
||||||
@@ -298,7 +299,7 @@ public class NativeToJsMessageQueue {
|
|||||||
public void run() {
|
public void run() {
|
||||||
String js = popAndEncodeAsJs();
|
String js = popAndEncodeAsJs();
|
||||||
if (js != null) {
|
if (js != null) {
|
||||||
webView.loadUrlNow("javascript:" + js);
|
webView.loadUrlIntoView("javascript:" + js, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -419,53 +420,43 @@ public class NativeToJsMessageQueue {
|
|||||||
this.pluginResult = pluginResult;
|
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() {
|
int calculateEncodedLength() {
|
||||||
if (pluginResult == null) {
|
if (pluginResult == null) {
|
||||||
return jsPayloadOrCallbackId.length() + 1;
|
return jsPayloadOrCallbackId.length() + 1;
|
||||||
}
|
}
|
||||||
int statusLen = String.valueOf(pluginResult.getStatus()).length();
|
int statusLen = String.valueOf(pluginResult.getStatus()).length();
|
||||||
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
|
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
|
||||||
switch (pluginResult.getMessageType()) {
|
return ret + calculateEncodedLengthHelper(pluginResult);
|
||||||
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
|
|
||||||
case PluginResult.MESSAGE_TYPE_NULL: // N
|
|
||||||
ret += 1;
|
|
||||||
break;
|
|
||||||
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
|
||||||
ret += 1 + pluginResult.getMessage().length();
|
|
||||||
break;
|
|
||||||
case PluginResult.MESSAGE_TYPE_STRING: // s
|
|
||||||
ret += 1 + pluginResult.getStrMessage().length();
|
|
||||||
break;
|
|
||||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
|
||||||
ret += 1 + pluginResult.getMessage().length();
|
|
||||||
break;
|
|
||||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
|
||||||
ret += 1 + pluginResult.getMessage().length();
|
|
||||||
break;
|
|
||||||
case PluginResult.MESSAGE_TYPE_JSON:
|
|
||||||
default:
|
|
||||||
ret += pluginResult.getMessage().length();
|
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
|
||||||
.append(keepCallback ? '1' : '0')
|
|
||||||
.append(status)
|
|
||||||
.append(' ')
|
|
||||||
.append(jsPayloadOrCallbackId)
|
|
||||||
.append(' ');
|
|
||||||
switch (pluginResult.getMessageType()) {
|
switch (pluginResult.getMessageType()) {
|
||||||
case PluginResult.MESSAGE_TYPE_BOOLEAN:
|
case PluginResult.MESSAGE_TYPE_BOOLEAN:
|
||||||
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
|
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
|
||||||
@@ -489,12 +480,42 @@ public class NativeToJsMessageQueue {
|
|||||||
sb.append('A');
|
sb.append('A');
|
||||||
sb.append(pluginResult.getMessage());
|
sb.append(pluginResult.getMessage());
|
||||||
break;
|
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:
|
case PluginResult.MESSAGE_TYPE_JSON:
|
||||||
default:
|
default:
|
||||||
sb.append(pluginResult.getMessage()); // [ or {
|
sb.append(pluginResult.getMessage()); // [ or {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void encodeAsMessage(StringBuilder sb) {
|
||||||
|
if (pluginResult == null) {
|
||||||
|
sb.append('J')
|
||||||
|
.append(jsPayloadOrCallbackId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int status = pluginResult.getStatus();
|
||||||
|
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
|
||||||
|
boolean resultOk = status == PluginResult.Status.OK.ordinal();
|
||||||
|
boolean keepCallback = pluginResult.getKeepCallback();
|
||||||
|
|
||||||
|
sb.append((noResult || resultOk) ? 'S' : 'F')
|
||||||
|
.append(keepCallback ? '1' : '0')
|
||||||
|
.append(status)
|
||||||
|
.append(' ')
|
||||||
|
.append(jsPayloadOrCallbackId)
|
||||||
|
.append(' ');
|
||||||
|
|
||||||
|
encodeAsMessageHelper(sb, pluginResult);
|
||||||
|
}
|
||||||
|
|
||||||
void encodeAsJsMessage(StringBuilder sb) {
|
void encodeAsJsMessage(StringBuilder sb) {
|
||||||
if (pluginResult == null) {
|
if (pluginResult == null) {
|
||||||
sb.append(jsPayloadOrCallbackId);
|
sb.append(jsPayloadOrCallbackId);
|
||||||
|
|||||||
@@ -18,43 +18,38 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.cordova.CordovaPlugin;
|
import org.apache.cordova.CordovaPlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a service entry object.
|
* This class represents a service entry object.
|
||||||
*/
|
*/
|
||||||
public class PluginEntry {
|
public final class PluginEntry {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the service that this plugin implements
|
* The name of the service that this plugin implements
|
||||||
*/
|
*/
|
||||||
public String service;
|
public final String service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The plugin class name that implements the service.
|
* The plugin class name that implements the service.
|
||||||
*/
|
*/
|
||||||
public String pluginClass;
|
public final String pluginClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pre-instantiated plugin to use for this entry.
|
* The pre-instantiated plugin to use for this entry.
|
||||||
*/
|
*/
|
||||||
public CordovaPlugin plugin;
|
public final CordovaPlugin plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag that indicates the plugin object should be created when PluginManager is initialized.
|
* Flag that indicates the plugin object should be created when PluginManager is initialized.
|
||||||
*/
|
*/
|
||||||
public boolean onload;
|
public final boolean onload;
|
||||||
|
|
||||||
private List<String> urlFilters;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs with a CordovaPlugin already instantiated.
|
* Constructs with a CordovaPlugin already instantiated.
|
||||||
*/
|
*/
|
||||||
public PluginEntry(String service, CordovaPlugin plugin) {
|
public PluginEntry(String service, CordovaPlugin plugin) {
|
||||||
this(service, plugin.getClass().getName(), true, plugin, null);
|
this(service, plugin.getClass().getName(), true, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,27 +58,13 @@ public class PluginEntry {
|
|||||||
* @param onload Create plugin object when HTML page is loaded
|
* @param onload Create plugin object when HTML page is loaded
|
||||||
*/
|
*/
|
||||||
public PluginEntry(String service, String pluginClass, boolean onload) {
|
public PluginEntry(String service, String pluginClass, boolean onload) {
|
||||||
this(service, pluginClass, onload, null, null);
|
this(service, pluginClass, onload, null);
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated // urlFilters are going away
|
|
||||||
public PluginEntry(String service, String pluginClass, boolean onload, List<String> urlFilters) {
|
|
||||||
this.service = service;
|
|
||||||
this.pluginClass = pluginClass;
|
|
||||||
this.onload = onload;
|
|
||||||
this.urlFilters = urlFilters;
|
|
||||||
plugin = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin, List<String> urlFilters) {
|
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.pluginClass = pluginClass;
|
this.pluginClass = pluginClass;
|
||||||
this.onload = onload;
|
this.onload = onload;
|
||||||
this.urlFilters = urlFilters;
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getUrlFilters() {
|
|
||||||
return urlFilters;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.cordova.CordovaWebView;
|
import org.apache.cordova.CordovaWebView;
|
||||||
import org.apache.cordova.CallbackContext;
|
import org.apache.cordova.CallbackContext;
|
||||||
@@ -51,31 +51,21 @@ public class PluginManager {
|
|||||||
private final CordovaInterface ctx;
|
private final CordovaInterface ctx;
|
||||||
private final CordovaWebView app;
|
private final CordovaWebView app;
|
||||||
|
|
||||||
// Stores mapping of Plugin Name -> <url-filter> values.
|
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||||
// Using <url-filter> is deprecated.
|
|
||||||
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova) {
|
|
||||||
this(cordovaWebView, cordova, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, List<PluginEntry> pluginEntries) {
|
|
||||||
this.ctx = cordova;
|
this.ctx = cordova;
|
||||||
this.app = cordovaWebView;
|
this.app = cordovaWebView;
|
||||||
if (pluginEntries == null) {
|
|
||||||
ConfigXmlParser parser = new ConfigXmlParser();
|
|
||||||
parser.parse(ctx.getActivity());
|
|
||||||
pluginEntries = parser.getPluginEntries();
|
|
||||||
}
|
|
||||||
setPluginEntries(pluginEntries);
|
setPluginEntries(pluginEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPluginEntries(List<PluginEntry> pluginEntries) {
|
public Collection<PluginEntry> getPluginEntries() {
|
||||||
|
return entryMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||||
this.onPause(false);
|
this.onPause(false);
|
||||||
this.onDestroy();
|
this.onDestroy();
|
||||||
pluginMap.clear();
|
pluginMap.clear();
|
||||||
urlMap.clear();
|
entryMap.clear();
|
||||||
for (PluginEntry entry : pluginEntries) {
|
for (PluginEntry entry : pluginEntries) {
|
||||||
addService(entry);
|
addService(entry);
|
||||||
}
|
}
|
||||||
@@ -92,23 +82,10 @@ public class PluginManager {
|
|||||||
this.startupPlugins();
|
this.startupPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public void loadPlugins() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all plugin objects.
|
|
||||||
*/
|
|
||||||
@Deprecated // Should not be exposed as public.
|
|
||||||
public void clearPluginObjects() {
|
|
||||||
pluginMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create plugins objects that have onload set.
|
* Create plugins objects that have onload set.
|
||||||
*/
|
*/
|
||||||
@Deprecated // Should not be exposed as public.
|
private void startupPlugins() {
|
||||||
public void startupPlugins() {
|
|
||||||
for (PluginEntry entry : entryMap.values()) {
|
for (PluginEntry entry : entryMap.values()) {
|
||||||
if (entry.onload) {
|
if (entry.onload) {
|
||||||
getPlugin(entry.service);
|
getPlugin(entry.service);
|
||||||
@@ -163,11 +140,6 @@ public class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
|
|
||||||
exec(service, action, callbackId, jsonArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the plugin object that implements the service.
|
* Get the plugin object that implements the service.
|
||||||
* If the plugin object does not already exist, then create it.
|
* If the plugin object does not already exist, then create it.
|
||||||
@@ -214,15 +186,10 @@ public class PluginManager {
|
|||||||
*/
|
*/
|
||||||
public void addService(PluginEntry entry) {
|
public void addService(PluginEntry entry) {
|
||||||
this.entryMap.put(entry.service, entry);
|
this.entryMap.put(entry.service, entry);
|
||||||
List<String> urlFilters = entry.getUrlFilters();
|
|
||||||
if (urlFilters != null) {
|
|
||||||
urlMap.put(entry.service, urlFilters);
|
|
||||||
}
|
|
||||||
if (entry.plugin != null) {
|
if (entry.plugin != null) {
|
||||||
entry.plugin.privateInitialize(ctx, app, app.getPreferences());
|
entry.plugin.privateInitialize(ctx, app, app.getPreferences());
|
||||||
pluginMap.put(entry.service, entry.plugin);
|
pluginMap.put(entry.service, entry.plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -286,6 +253,110 @@ public class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going to request an external resource.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, which must all return true for
|
||||||
|
* this method to return true.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Tri-State:
|
||||||
|
* null: All plugins returned null (the default). This
|
||||||
|
* indicates that the default policy should be
|
||||||
|
* followed.
|
||||||
|
* true: All plugins returned true (allow the resource
|
||||||
|
* to load)
|
||||||
|
* false: At least one plugin returned false (block the
|
||||||
|
* resource)
|
||||||
|
*/
|
||||||
|
public Boolean shouldAllowRequest(String url) {
|
||||||
|
Boolean anyResponded = null;
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldAllowRequest(url);
|
||||||
|
if (result != null) {
|
||||||
|
anyResponded = true;
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||||
|
return anyResponded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going to change the URL of the loaded content.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, which must all return true for
|
||||||
|
* this method to return true. A true result will allow the new page to load;
|
||||||
|
* a false result will prevent the page from loading.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Tri-State:
|
||||||
|
* null: All plugins returned null (the default). This
|
||||||
|
* indicates that the default policy should be
|
||||||
|
* followed.
|
||||||
|
* true: All plugins returned true (allow the navigation)
|
||||||
|
* false: At least one plugin returned false (block the
|
||||||
|
* navigation)
|
||||||
|
*/
|
||||||
|
public Boolean shouldAllowNavigation(String url) {
|
||||||
|
Boolean anyResponded = null;
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldAllowNavigation(url);
|
||||||
|
if (result != null) {
|
||||||
|
anyResponded = true;
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||||
|
return anyResponded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the webview is going not going to navigate, but may launch
|
||||||
|
* an Intent for an URL.
|
||||||
|
*
|
||||||
|
* This delegates to the installed plugins, which must all return true for
|
||||||
|
* this method to return true. A true result will allow the URL to launch;
|
||||||
|
* a false result will prevent the URL from loading.
|
||||||
|
*
|
||||||
|
* @param url The URL that is being requested.
|
||||||
|
* @return Tri-State:
|
||||||
|
* null: All plugins returned null (the default). This
|
||||||
|
* indicates that the default policy should be
|
||||||
|
* followed.
|
||||||
|
* true: All plugins returned true (allow the URL to
|
||||||
|
* launch an intent)
|
||||||
|
* false: At least one plugin returned false (block the
|
||||||
|
* intent)
|
||||||
|
*/
|
||||||
|
public Boolean shouldOpenExternalUrl(String url) {
|
||||||
|
Boolean anyResponded = null;
|
||||||
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
|
if (plugin != null) {
|
||||||
|
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||||
|
if (result != null) {
|
||||||
|
anyResponded = true;
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This will be true if all plugins allow the request, or null if no plugins override the method
|
||||||
|
return anyResponded;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the URL of the webview changes.
|
* Called when the URL of the webview changes.
|
||||||
*
|
*
|
||||||
@@ -298,18 +369,9 @@ public class PluginManager {
|
|||||||
// that they are loaded before this function is called (either by setting
|
// that they are loaded before this function is called (either by setting
|
||||||
// the onload <param> or by making an exec() call to them)
|
// the onload <param> or by making an exec() call to them)
|
||||||
for (PluginEntry entry : this.entryMap.values()) {
|
for (PluginEntry entry : this.entryMap.values()) {
|
||||||
List<String> urlFilters = urlMap.get(entry.service);
|
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||||
if (urlFilters != null) {
|
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||||
for (String s : urlFilters) {
|
return true;
|
||||||
if (url.startsWith(s)) {
|
|
||||||
return getPlugin(entry.service).onOverrideUrlLoading(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
|
||||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
19
framework/src/org/apache/cordova/PluginResult.java
Executable file → Normal file
19
framework/src/org/apache/cordova/PluginResult.java
Executable file → Normal file
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ public class PluginResult {
|
|||||||
private boolean keepCallback = false;
|
private boolean keepCallback = false;
|
||||||
private String strMessage;
|
private String strMessage;
|
||||||
private String encodedMessage;
|
private String encodedMessage;
|
||||||
|
private List<PluginResult> multipartMessages;
|
||||||
|
|
||||||
public PluginResult(Status status) {
|
public PluginResult(Status status) {
|
||||||
this(status, PluginResult.StatusMessages[status.ordinal()]);
|
this(status, PluginResult.StatusMessages[status.ordinal()]);
|
||||||
@@ -80,6 +83,13 @@ public class PluginResult {
|
|||||||
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
|
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) {
|
public void setKeepCallback(boolean b) {
|
||||||
this.keepCallback = b;
|
this.keepCallback = b;
|
||||||
}
|
}
|
||||||
@@ -99,6 +109,14 @@ public class PluginResult {
|
|||||||
return encodedMessage;
|
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.
|
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
|
||||||
* Otherwise, returns null.
|
* Otherwise, returns null.
|
||||||
@@ -150,6 +168,7 @@ public class PluginResult {
|
|||||||
// Use BINARYSTRING when your string may contain null characters.
|
// Use BINARYSTRING when your string may contain null characters.
|
||||||
// This is required to work around a bug in the platform :(.
|
// 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_BINARYSTRING = 7;
|
||||||
|
public static final int MESSAGE_TYPE_MULTIPART = 8;
|
||||||
|
|
||||||
public static String[] StatusMessages = new String[] {
|
public static String[] StatusMessages = new String[] {
|
||||||
"No result",
|
"No result",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class ScrollEvent {
|
|||||||
* @param view
|
* @param view
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ScrollEvent(int nx, int ny, int x, int y, View view)
|
public ScrollEvent(int nx, int ny, int x, int y, View view)
|
||||||
{
|
{
|
||||||
l = x; y = t; nl = nx; nt = ny;
|
l = x; y = t; nl = nx; nt = ny;
|
||||||
targetView = view;
|
targetView = view;
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cordova-android",
|
"name": "cordova-android",
|
||||||
"version": "3.6.4",
|
"version": "4.0.0-dev",
|
||||||
"description": "cordova-android release",
|
"description": "cordova-android release",
|
||||||
"main": "bin/create",
|
"main": "bin/create",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -13,16 +13,18 @@
|
|||||||
"apache"
|
"apache"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jasmine-node --color spec"
|
"test": "jasmine-node --color spec",
|
||||||
|
"test-build": "rm -rf \"test create\"; ./bin/create \"test create\" com.test.app 応用 && \"./test create/cordova/build\" && rm -rf \"test create\""
|
||||||
},
|
},
|
||||||
"author": "Apache Software Foundation",
|
"author": "Apache Software Foundation",
|
||||||
"license": "Apache version 2.0",
|
"license": "Apache version 2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"q": "^0.9.0",
|
"q": "^0.9.0",
|
||||||
"shelljs": "^0.2.6"
|
"shelljs": "^0.2.6",
|
||||||
|
"which": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jasmine-node": "~1",
|
"jasmine-node": "~1",
|
||||||
"promise-matchers": "~0"
|
"promise-matchers": "~0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ found at https://code.google.com/p/robotium/ and the jar should be put in the
|
|||||||
To run manually from command line:
|
To run manually from command line:
|
||||||
|
|
||||||
0. Build by entering `ant debug install`
|
0. Build by entering `ant debug install`
|
||||||
0. Run tests by clicking on "CordovaTest" icon on device
|
0. Run tests by clicking on "CordovaNativeTests" app icon on the device
|
||||||
|
|
||||||
To run from Eclipse:
|
To run from Eclipse:
|
||||||
|
|
||||||
|
|||||||
8
test/assets/www/error.html
Normal file
8
test/assets/www/error.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OH NOES!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Things went terribly wrong!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<org.apache.cordova.CordovaWebView
|
<org.apache.cordova.AndroidWebView
|
||||||
android:id="@+id/cordovaWebView"
|
android:id="@+id/cordovaWebView"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent" />
|
android:layout_height="fill_parent" />
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ package org.apache.cordova.test;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.apache.cordova.AndroidChromeClient;
|
||||||
|
import org.apache.cordova.AndroidWebViewClient;
|
||||||
import org.apache.cordova.Config;
|
import org.apache.cordova.Config;
|
||||||
import org.apache.cordova.CordovaChromeClient;
|
|
||||||
import org.apache.cordova.CordovaWebView;
|
import org.apache.cordova.CordovaWebView;
|
||||||
import org.apache.cordova.CordovaInterface;
|
import org.apache.cordova.CordovaInterface;
|
||||||
import org.apache.cordova.CordovaPlugin;
|
import org.apache.cordova.CordovaPlugin;
|
||||||
import org.apache.cordova.CordovaWebViewClient;
|
|
||||||
import org.apache.cordova.test.R;
|
import org.apache.cordova.test.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -51,8 +51,7 @@ public class CordovaWebViewTestActivity extends Activity implements CordovaInter
|
|||||||
Config.init(this);
|
Config.init(this);
|
||||||
|
|
||||||
cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView);
|
cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView);
|
||||||
cordovaWebView.init(this, new CordovaWebViewClient(this, cordovaWebView), new CordovaChromeClient(this, cordovaWebView),
|
cordovaWebView.init(this, Config.getPluginEntries(), Config.getPreferences());
|
||||||
Config.getPluginEntries(), Config.getWhitelist(), Config.getExternalWhitelist(), Config.getPreferences());
|
|
||||||
|
|
||||||
cordovaWebView.loadUrl("file:///android_asset/www/index.html");
|
cordovaWebView.loadUrl("file:///android_asset/www/index.html");
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +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.test;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import org.apache.cordova.*;
|
|
||||||
|
|
||||||
public class basicauth extends CordovaActivity {
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
super.init();
|
|
||||||
|
|
||||||
// LogCat: onReceivedHttpAuthRequest(browserspy.dk:80,BrowserSpy.dk - HTTP Password Test)
|
|
||||||
AuthenticationToken token = new AuthenticationToken();
|
|
||||||
token.setUserName("test");
|
|
||||||
token.setPassword("test");
|
|
||||||
// classic webview includes port in hostname, Chromium webview does not. Handle both here.
|
|
||||||
// BTW, the realm is optional.
|
|
||||||
setAuthenticationToken(token, "browserspy.dk:80", "BrowserSpy.dk - HTTP Password Test");
|
|
||||||
setAuthenticationToken(token, "browserspy.dk", "BrowserSpy.dk - HTTP Password Test");
|
|
||||||
|
|
||||||
// Add web site to whitelist
|
|
||||||
Config.getWhitelist().addWhiteListEntry("http://browserspy.dk/*", true);
|
|
||||||
|
|
||||||
// Load test
|
|
||||||
super.loadUrl("file:///android_asset/www/basicauth/index.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,7 @@ import org.apache.cordova.test.backbuttonmultipage;
|
|||||||
import android.test.ActivityInstrumentationTestCase2;
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
import android.test.UiThreadTest;
|
import android.test.UiThreadTest;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
import android.view.inputmethod.BaseInputConnection;
|
import android.view.inputmethod.BaseInputConnection;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@@ -174,7 +175,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
|
|||||||
{
|
{
|
||||||
String url = testView.getUrl();
|
String url = testView.getUrl();
|
||||||
assertTrue(url.endsWith("sample3.html"));
|
assertTrue(url.endsWith("sample3.html"));
|
||||||
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
|
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
|
||||||
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||||
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||||
viewConnection.sendKeyEvent(backDown);
|
viewConnection.sendKeyEvent(backDown);
|
||||||
@@ -187,7 +188,7 @@ public class BackButtonMultiPageTest extends ActivityInstrumentationTestCase2<ba
|
|||||||
{
|
{
|
||||||
String url = testView.getUrl();
|
String url = testView.getUrl();
|
||||||
assertTrue(url.endsWith("sample2.html"));
|
assertTrue(url.endsWith("sample2.html"));
|
||||||
BaseInputConnection viewConnection = new BaseInputConnection(testView, true);
|
BaseInputConnection viewConnection = new BaseInputConnection((View) testView, true);
|
||||||
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
KeyEvent backDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
|
||||||
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
KeyEvent backUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
|
||||||
viewConnection.sendKeyEvent(backDown);
|
viewConnection.sendKeyEvent(backDown);
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ public class CordovaActivityTest extends ActivityInstrumentationTestCase2<MainTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testForCordovaView() {
|
public void testForAndroidWebView() {
|
||||||
String className = testView.getClass().getSimpleName();
|
String className = testView.getClass().getSimpleName();
|
||||||
assertTrue(className.equals("CordovaWebView"));
|
assertTrue(className.equals("AndroidWebView"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testForLinearLayout() {
|
public void testForLinearLayout() {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class CordovaResourceApiTest extends ActivityInstrumentationTestCase2<Cor
|
|||||||
cordovaWebView = activity.cordovaWebView;
|
cordovaWebView = activity.cordovaWebView;
|
||||||
resourceApi = cordovaWebView.getResourceApi();
|
resourceApi = cordovaWebView.getResourceApi();
|
||||||
resourceApi.setThreadCheckingEnabled(false);
|
resourceApi.setThreadCheckingEnabled(false);
|
||||||
cordovaWebView.pluginManager.addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
|
cordovaWebView.getPluginManager().addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public Uri remapUri(Uri uri) {
|
public Uri remapUri(Uri uri) {
|
||||||
if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {
|
if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ public class CordovaTest extends
|
|||||||
assertNotNull(testView);
|
assertNotNull(testView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testForCordovaView() {
|
public void testForAndroidWebView() {
|
||||||
//Sleep for no reason!!!!
|
//Sleep for no reason!!!!
|
||||||
sleep();
|
sleep();
|
||||||
String className = testView.getClass().getSimpleName();
|
String className = testView.getClass().getSimpleName();
|
||||||
assertTrue(className.equals("CordovaWebView"));
|
assertTrue(className.equals("AndroidWebView"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
66
test/src/org/apache/cordova/test/junit/GapClientTest.java
Normal file
66
test/src/org/apache/cordova/test/junit/GapClientTest.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cordova.test.junit;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.PluginManager;
|
||||||
|
import org.apache.cordova.test.CordovaWebViewTestActivity;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
public class GapClientTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
|
||||||
|
|
||||||
|
private CordovaWebViewTestActivity testActivity;
|
||||||
|
private FrameLayout containerView;
|
||||||
|
private LinearLayout innerContainer;
|
||||||
|
private View testView;
|
||||||
|
private String rString;
|
||||||
|
|
||||||
|
public GapClientTest() {
|
||||||
|
super("org.apache.cordova.test.activities",CordovaWebViewTestActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setUp() throws Exception{
|
||||||
|
super.setUp();
|
||||||
|
testActivity = this.getActivity();
|
||||||
|
containerView = (FrameLayout) testActivity.findViewById(android.R.id.content);
|
||||||
|
innerContainer = (LinearLayout) containerView.getChildAt(0);
|
||||||
|
testView = innerContainer.getChildAt(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPreconditions(){
|
||||||
|
assertNotNull(innerContainer);
|
||||||
|
assertNotNull(testView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testForAndroidWebView() {
|
||||||
|
String className = testView.getClass().getSimpleName();
|
||||||
|
assertTrue(className.equals("AndroidWebView"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,32 +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.test.junit;
|
|
||||||
|
|
||||||
import org.apache.cordova.test.menus;
|
|
||||||
|
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
|
||||||
|
|
||||||
public class MenuTest extends ActivityInstrumentationTestCase2<menus> {
|
|
||||||
|
|
||||||
public MenuTest() {
|
|
||||||
super("org.apache.cordova.test", menus.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ public class menus extends CordovaActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
// need the title to be shown (config.xml) for the options menu to be visible
|
// need the title to be shown (config.xml) for the options menu to be visible
|
||||||
super.init();
|
super.init();
|
||||||
super.registerForContextMenu(super.appView);
|
super.registerForContextMenu(super.appView.getView());
|
||||||
super.loadUrl("file:///android_asset/www/menus/index.html");
|
super.loadUrl("file:///android_asset/www/menus/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,16 +32,16 @@ public class userwebview extends MainTestActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
testViewClient = new TestViewClient(this, appView);
|
testViewClient = new TestViewClient(this, ((AndroidWebView)appView));
|
||||||
testChromeClient = new TestChromeClient(this, appView);
|
testChromeClient = new TestChromeClient(this, ((AndroidWebView)appView));
|
||||||
super.init();
|
super.init();
|
||||||
appView.setWebViewClient(testViewClient);
|
((AndroidWebView)appView).setWebViewClient(testViewClient);
|
||||||
appView.setWebChromeClient(testChromeClient);
|
((AndroidWebView)appView).setWebChromeClient(testChromeClient);
|
||||||
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestChromeClient extends CordovaChromeClient {
|
public class TestChromeClient extends AndroidChromeClient {
|
||||||
public TestChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
public TestChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
||||||
super(ctx, app);
|
super(ctx, app);
|
||||||
LOG.d("userwebview", "TestChromeClient()");
|
LOG.d("userwebview", "TestChromeClient()");
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ public class userwebview extends MainTestActivity {
|
|||||||
/**
|
/**
|
||||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||||
*/
|
*/
|
||||||
public class TestViewClient extends CordovaWebViewClient {
|
public class TestViewClient extends AndroidWebViewClient {
|
||||||
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
|
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
||||||
super(ctx, app);
|
super(ctx, app);
|
||||||
LOG.d("userwebview", "TestViewClient()");
|
LOG.d("userwebview", "TestViewClient()");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,22 @@ import android.os.Bundle;
|
|||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
import org.apache.cordova.*;
|
import org.apache.cordova.*;
|
||||||
import org.apache.cordova.LOG;
|
|
||||||
|
|
||||||
public class whitelist extends MainTestActivity {
|
public class whitelist extends MainTestActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
super.init();
|
super.init();
|
||||||
appView.setWebViewClient(new TestViewClient(this, appView));
|
((AndroidWebView)appView).setWebViewClient(new TestViewClient(this, ((AndroidWebView)appView)));
|
||||||
super.loadUrl("file:///android_asset/www/whitelist/index.html");
|
super.loadUrl("file:///android_asset/www/whitelist/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||||
*/
|
*/
|
||||||
public class TestViewClient extends CordovaWebViewClient {
|
public class TestViewClient extends AndroidWebViewClient {
|
||||||
|
|
||||||
public TestViewClient(CordovaInterface ctx, CordovaWebView app) {
|
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
||||||
super(ctx, app);
|
super(ctx, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user