Merge branch 'master' into 4.0.x (bindButton changes)

Conflicts:
	framework/src/org/apache/cordova/CordovaWebView.java
	package.json
This commit is contained in:
Andrew Grieve 2014-06-24 15:22:27 -04:00
commit 4ce5123a12
7 changed files with 178 additions and 87 deletions

View File

@ -116,6 +116,50 @@ function copyGradleWrapper(sdkPath, projectPath) {
shell.cp('-r', path.join(wrapperDir, 'gradle'), projectPath); shell.cp('-r', path.join(wrapperDir, 'gradle'), projectPath);
} }
/**
* Test whether a package name is acceptable for use as an android project.
* Returns a promise, fulfilled if the package name is acceptable; rejected
* otherwise.
*/
function validatePackageName(package_name) {
//Make the package conform to Java package types
//Enforce underscore limitation
if (!/^[a-zA-Z]+(\.[a-zA-Z0-9][a-zA-Z0-9_]*)+$/.test(package_name)) {
return Q.reject('Package name must look like: com.company.Name');
}
//Class is a reserved word
if(/\b[Cc]lass\b/.test(package_name)) {
return Q.reject('class is a reserved word');
}
return Q.resolve();
}
/**
* Test whether a project name is acceptable for use as an android class.
* Returns a promise, fulfilled if the project name is acceptable; rejected
* otherwise.
*/
function validateProjectName(project_name) {
//Make sure there's something there
if (project_name === '') {
return Q.reject('Project name cannot be empty');
}
//Enforce stupid name error
if (project_name === 'CordovaActivity') {
return Q.reject('Project name cannot be CordovaActivity');
}
//Classes in Java don't begin with numbers
if (/^[0-9]/.test(project_name)) {
return Q.reject('Project name must not begin with a number');
}
return Q.resolve();
}
/** /**
* $ create [options] * $ create [options]
* *
@ -156,34 +200,14 @@ exports.createProject = function(project_path, package_name, project_name, proje
} }
//Make the package conform to Java package types //Make the package conform to Java package types
if (!/[a-zA-Z0-9_]+\.[a-zA-Z0-9_](.[a-zA-Z0-9_])*/.test(package_name)) { return validatePackageName(package_name)
return Q.reject('Package name must look like: com.company.Name');
}
//Enforce underscore limitation
if (/[_]+[a-zA-Z0-9_]*/.test(package_name)) {
return Q.reject("Package name can't begin with an underscore");
}
//Enforce stupid name error
if (project_name === 'CordovaActivity') {
return Q.reject('Project name cannot be CordovaActivity');
}
//Classes in Java don't begin with numbers
if (/[0-9]+[a-zA-Z0-9]/.test(project_name)) {
return Q.reject('Project name must not begin with a number');
}
//Class is a reserved word
if(/[C|c]+lass+[\s|\.]/.test(package_name) && !/[a-zA-Z0-9_]+[C|c]+lass/.test(package_name))
{
return Q.reject('class is a reserved word');
}
// Check that requirements are met and proper targets are installed
return check_reqs.run()
.then(function() { .then(function() {
validateProjectName(project_name);
})
// Check that requirements are met and proper targets are installed
.then(function() {
check_reqs.run();
}).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:');
console.log('\tPath: ' + project_path); console.log('\tPath: ' + project_path);
@ -284,3 +308,7 @@ exports.updateProject = function(projectPath) {
}); });
}; };
// For testing
exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName;

View File

@ -21,8 +21,8 @@ package org.apache.cordova;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import org.apache.cordova.Config; import org.apache.cordova.Config;
@ -72,8 +72,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
public static final String TAG = "CordovaWebView"; public static final String TAG = "CordovaWebView";
public static final String CORDOVA_VERSION = "4.0.0-dev"; public static final String CORDOVA_VERSION = "4.0.0-dev";
private ArrayList<Integer> keyDownCodes = new ArrayList<Integer>(); private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
PluginManager pluginManager; PluginManager pluginManager;
private boolean paused; private boolean paused;
@ -92,10 +91,6 @@ public class AndroidWebView extends WebView implements CordovaWebView {
// Flag to track that a loadUrl timeout occurred // Flag to track that a loadUrl timeout occurred
int loadUrlTimeout = 0; int loadUrlTimeout = 0;
private boolean bound;
private boolean handleButton = false;
private long lastMenuEventTime = 0; private long lastMenuEventTime = 0;
NativeToJsMessageQueue jsMessageQueue; NativeToJsMessageQueue jsMessageQueue;
@ -708,17 +703,13 @@ public class AndroidWebView extends WebView implements CordovaWebView {
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) public boolean onKeyDown(int keyCode, KeyEvent event)
{ {
if(keyDownCodes.contains(keyCode)) if(boundKeyCodes.contains(keyCode))
{ {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
// only override default behavior is event bound
LOG.d(TAG, "Down Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');"); this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true; return true;
} }
// If volumeup key
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
LOG.d(TAG, "Up Key Hit");
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');"); this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
return true; return true;
} }
@ -729,7 +720,8 @@ public class AndroidWebView extends WebView implements CordovaWebView {
} }
else if(keyCode == KeyEvent.KEYCODE_BACK) else if(keyCode == KeyEvent.KEYCODE_BACK)
{ {
return !(this.startOfHistory()) || this.bound; return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
} }
else if(keyCode == KeyEvent.KEYCODE_MENU) else if(keyCode == KeyEvent.KEYCODE_MENU)
{ {
@ -746,10 +738,8 @@ public class AndroidWebView extends WebView implements CordovaWebView {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
} }
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) public boolean onKeyUp(int keyCode, KeyEvent event)
@ -759,10 +749,11 @@ public class AndroidWebView extends WebView implements CordovaWebView {
// A custom view is currently displayed (e.g. playing a video) // A custom view is currently displayed (e.g. playing a video)
if(mCustomView != null) { if(mCustomView != null) {
this.hideCustomView(); this.hideCustomView();
return true;
} else { } else {
// The webview is currently displayed // The webview is currently displayed
// If back key is bound, then send event to JavaScript // If back key is bound, then send event to JavaScript
if (this.bound) { if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');"); this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
return true; return true;
} else { } else {
@ -788,48 +779,31 @@ public class AndroidWebView extends WebView implements CordovaWebView {
this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');"); this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
return true; return true;
} }
else if(keyUpCodes.contains(keyCode))
{
//What the hell should this do?
return super.onKeyUp(keyCode, event);
}
//Does webkit change this behavior? //Does webkit change this behavior?
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
@Override
public void bindButton(boolean override) public void setButtonPlumbedToJs(int keyCode, boolean value) {
{ switch (keyCode) {
this.bound = override; case KeyEvent.KEYCODE_VOLUME_DOWN:
} case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_BACK:
public void bindButton(String button, boolean override) { // TODO: Why are search and menu buttons handled separately?
// TODO Auto-generated method stub boundKeyCodes.add(keyCode);
if (button.compareTo("volumeup")==0) { return;
keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_UP); default:
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
} }
else if (button.compareTo("volumedown")==0) {
keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_DOWN);
}
}
public void bindButton(int keyCode, boolean keyDown, boolean override) {
if(keyDown)
{
keyDownCodes.add(keyCode);
}
else
{
keyUpCodes.add(keyCode);
}
} }
public boolean isBackButtonBound() @Override
public boolean isButtonPlumbedToJs(int keyCode)
{ {
return this.bound; return boundKeyCodes.contains(keyCode);
} }
public void handlePause(boolean keepRunning) public void handlePause(boolean keepRunning)
{ {
LOG.d(TAG, "Handle the pause"); LOG.d(TAG, "Handle the pause");
@ -901,10 +875,6 @@ public class AndroidWebView extends WebView implements CordovaWebView {
return paused; return paused;
} }
public boolean hadKeyEvent() {
return handleButton;
}
// Wrapping these functions in their own class prevents warnings in adb like: // Wrapping these functions in their own class prevents warnings in adb like:
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs // VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
@TargetApi(16) @TargetApi(16)

View File

@ -136,10 +136,8 @@ public interface CordovaWebView {
CordovaResourceApi getResourceApi(); CordovaResourceApi getResourceApi();
void bindButton(boolean override); void setButtonPlumbedToJs(int keyCode, boolean override);
void bindButton(String button, boolean override); boolean isButtonPlumbedToJs(int keyCode);
boolean isBackButtonBound();
void sendPluginResult(PluginResult cr, String callbackId); void sendPluginResult(PluginResult cr, String callbackId);
@ -148,7 +146,6 @@ public interface CordovaWebView {
void setLayoutParams(android.widget.FrameLayout.LayoutParams layoutParams); void setLayoutParams(android.widget.FrameLayout.LayoutParams layoutParams);
// Required for test // Required for test
String getUrl(); String getUrl();
boolean isPaused(); boolean isPaused();
} }

View File

@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import java.util.HashMap; import java.util.HashMap;
@ -217,7 +218,7 @@ public class CoreAndroid extends CordovaPlugin {
*/ */
public void overrideBackbutton(boolean override) { public void overrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!"); LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
webView.bindButton(override); webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
} }
/** /**
@ -229,7 +230,12 @@ public class CoreAndroid extends CordovaPlugin {
*/ */
public void overrideButton(String button, boolean override) { public void overrideButton(String button, boolean override) {
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!"); LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
webView.bindButton(button, override); if (button.equals("volumeup")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
}
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
} }
/** /**
@ -238,7 +244,7 @@ public class CoreAndroid extends CordovaPlugin {
* @return boolean * @return boolean
*/ */
public boolean isBackbuttonOverridden() { public boolean isBackbuttonOverridden() {
return webView.isBackButtonBound(); return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
} }
/** /**

View File

@ -171,6 +171,7 @@ public class PluginManager {
service = ""; service = "";
pluginClass = ""; pluginClass = "";
insideFeature = false; insideFeature = false;
onload = false;
} }
} }
try { try {

View File

@ -12,11 +12,18 @@
"cordova", "cordova",
"apache" "apache"
], ],
"scripts": {
"test": "jasmine-node --color spec"
},
"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" "which": "^1.0.5"
},
"devDependencies": {
"jasmine-node": "~1",
"promise-matchers": "~0"
} }
} }

82
spec/create.spec.js Normal file
View File

@ -0,0 +1,82 @@
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
/* jshint laxcomma:true */
require("promise-matchers");
var create = require("../bin/lib/create");
describe("create", function () {
describe("validatePackageName", function() {
var valid = [
"org.apache.mobilespec"
, "com.example"
, "com.42floors.package"
];
var invalid = [
""
, "com.class.is.bad"
, "0com.example.mobilespec"
, "c-m.e@a!p%e.mobilespec"
, "notenoughdots"
, ".starts.with.a.dot"
, "ends.with.a.dot."
, "_underscore.anything"
, "underscore._something"
, "_underscore._all._the._things"
];
valid.forEach(function(package_name) {
it("should accept " + package_name, function(done) {
expect(create.validatePackageName(package_name)).toHaveBeenResolved(done);
});
});
invalid.forEach(function(package_name) {
it("should reject " + package_name, function(done) {
expect(create.validatePackageName(package_name)).toHaveBeenRejected(done);
});
});
});
describe("validateProjectName", function() {
var valid = [
"mobilespec"
, "package_name"
, "PackageName"
, "CordovaLib"
];
var invalid = [
""
, "0startswithdigit"
, "CordovaActivity"
];
valid.forEach(function(project_name) {
it("should accept " + project_name, function(done) {
expect(create.validateProjectName(project_name)).toHaveBeenResolved(done);
});
});
invalid.forEach(function(project_name) {
it("should reject " + project_name, function(done) {
expect(create.validateProjectName(project_name)).toHaveBeenRejected(done);
});
});
});
});