diff --git a/bin/lib/create.js b/bin/lib/create.js index 5e21b894..485c60eb 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -116,6 +116,50 @@ function copyGradleWrapper(sdkPath, 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] * @@ -156,34 +200,14 @@ exports.createProject = function(project_path, package_name, project_name, proje } //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 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() + return validatePackageName(package_name) .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 console.log('Creating Cordova project for the Android platform:'); console.log('\tPath: ' + project_path); @@ -284,3 +308,7 @@ exports.updateProject = function(projectPath) { }); }; + +// For testing +exports.validatePackageName = validatePackageName; +exports.validateProjectName = validateProjectName; diff --git a/framework/src/org/apache/cordova/AndroidWebView.java b/framework/src/org/apache/cordova/AndroidWebView.java index 5001b641..49ec7823 100755 --- a/framework/src/org/apache/cordova/AndroidWebView.java +++ b/framework/src/org/apache/cordova/AndroidWebView.java @@ -21,8 +21,8 @@ package org.apache.cordova; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; 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 CORDOVA_VERSION = "4.0.0-dev"; - private ArrayList keyDownCodes = new ArrayList(); - private ArrayList keyUpCodes = new ArrayList(); + private HashSet boundKeyCodes = new HashSet(); PluginManager pluginManager; private boolean paused; @@ -92,10 +91,6 @@ public class AndroidWebView extends WebView implements CordovaWebView { // Flag to track that a loadUrl timeout occurred int loadUrlTimeout = 0; - private boolean bound; - - private boolean handleButton = false; - private long lastMenuEventTime = 0; NativeToJsMessageQueue jsMessageQueue; @@ -708,17 +703,13 @@ public class AndroidWebView extends WebView implements CordovaWebView { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if(keyDownCodes.contains(keyCode)) + if(boundKeyCodes.contains(keyCode)) { 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');"); return true; } - // If volumeup key else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - LOG.d(TAG, "Up Key Hit"); this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');"); return true; } @@ -729,7 +720,8 @@ public class AndroidWebView extends WebView implements CordovaWebView { } else if(keyCode == KeyEvent.KEYCODE_BACK) { - return !(this.startOfHistory()) || this.bound; + return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK); + } 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); } - @Override 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) if(mCustomView != null) { this.hideCustomView(); + return true; } else { // The webview is currently displayed // If back key is bound, then send event to JavaScript - if (this.bound) { + if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) { this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');"); return true; } else { @@ -788,48 +779,31 @@ public class AndroidWebView extends WebView implements CordovaWebView { this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');"); return true; } - else if(keyUpCodes.contains(keyCode)) - { - //What the hell should this do? - return super.onKeyUp(keyCode, event); - } //Does webkit change this behavior? return super.onKeyUp(keyCode, event); } - - public void bindButton(boolean override) - { - this.bound = override; - } - - public void bindButton(String button, boolean override) { - // TODO Auto-generated method stub - if (button.compareTo("volumeup")==0) { - keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_UP); + @Override + public void setButtonPlumbedToJs(int keyCode, boolean value) { + 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? + boundKeyCodes.add(keyCode); + return; + 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) { LOG.d(TAG, "Handle the pause"); @@ -901,10 +875,6 @@ public class AndroidWebView extends WebView implements CordovaWebView { return paused; } - public boolean hadKeyEvent() { - return handleButton; - } - // 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) diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index 70c60511..6a9fbaf3 100644 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -136,10 +136,8 @@ public interface CordovaWebView { CordovaResourceApi getResourceApi(); - void bindButton(boolean override); - void bindButton(String button, boolean override); - - boolean isBackButtonBound(); + void setButtonPlumbedToJs(int keyCode, boolean override); + boolean isButtonPlumbedToJs(int keyCode); void sendPluginResult(PluginResult cr, String callbackId); @@ -148,7 +146,6 @@ public interface CordovaWebView { void setLayoutParams(android.widget.FrameLayout.LayoutParams layoutParams); // Required for test - String getUrl(); boolean isPaused(); } diff --git a/framework/src/org/apache/cordova/CoreAndroid.java b/framework/src/org/apache/cordova/CoreAndroid.java index b47efa87..56c7e649 100755 --- a/framework/src/org/apache/cordova/CoreAndroid.java +++ b/framework/src/org/apache/cordova/CoreAndroid.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.telephony.TelephonyManager; +import android.view.KeyEvent; import java.util.HashMap; @@ -217,7 +218,7 @@ public class CoreAndroid extends CordovaPlugin { */ public void overrideBackbutton(boolean override) { 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) { 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 */ public boolean isBackbuttonOverridden() { - return webView.isBackButtonBound(); + return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK); } /** diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java index e6a83258..50fe6b79 100755 --- a/framework/src/org/apache/cordova/PluginManager.java +++ b/framework/src/org/apache/cordova/PluginManager.java @@ -171,6 +171,7 @@ public class PluginManager { service = ""; pluginClass = ""; insideFeature = false; + onload = false; } } try { diff --git a/package.json b/package.json index f38c1873..4dbd5ce7 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,18 @@ "cordova", "apache" ], + "scripts": { + "test": "jasmine-node --color spec" + }, "author": "Apache Software Foundation", "license": "Apache version 2.0", "dependencies": { "q": "^0.9.0", "shelljs": "^0.2.6", "which": "^1.0.5" + }, + "devDependencies": { + "jasmine-node": "~1", + "promise-matchers": "~0" } } diff --git a/spec/create.spec.js b/spec/create.spec.js new file mode 100644 index 00000000..8e08793f --- /dev/null +++ b/spec/create.spec.js @@ -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); + }); + }); + }); +});