summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/android
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/platforms/android')
-rwxr-xr-xStoneIsland/platforms/android/AndroidManifest.xml25
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/build.gradle28
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/cordova.gradle13
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/project.properties2
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java14
-rw-r--r--StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackMap.java65
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Config.java3
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ConfigXmlParser.java0
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java121
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java16
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java0
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java16
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java85
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java70
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java2
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java6
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java12
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java77
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java0
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/LOG.java10
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java71
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginManager.java29
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginResult.java0
-rw-r--r--StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ResumeCallback.java76
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Whitelist.java0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemCookieManager.java6
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebChromeClient.java41
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebView.java0
-rwxr-xr-xStoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebViewEngine.java58
-rwxr-xr-xStoneIsland/platforms/android/android.json79
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova-js-src/android/nativeapiprovider.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova-js-src/android/promptbasednativeapi.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova-js-src/exec.js20
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova-js-src/platform.js36
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova-js-src/plugin/android/app.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/assets/www/cordova.js291
-rwxr-xr-xStoneIsland/platforms/android/assets/www/cordova_plugins.js13
-rwxr-xr-xStoneIsland/platforms/android/assets/www/css/products.css7
-rw-r--r--StoneIsland/platforms/android/assets/www/img/angle-down.pngbin0 -> 5562 bytes
-rwxr-xr-xStoneIsland/platforms/android/assets/www/index.html3
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/index.js2
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/auth/SignupView.js2
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/blogs/BlogView.js8
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/blogs/HubView.js1
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/products/CollectionView.js9
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/products/ProductView.js4
-rwxr-xr-xStoneIsland/platforms/android/assets/www/js/lib/products/Selector.js4
-rw-r--r--StoneIsland/platforms/android/assets/www/js/lib/products/filters/DepartmentFilter.js31
-rw-r--r--StoneIsland/platforms/android/assets/www/plugins/phonegap-plugin-push/www/push.js329
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/build.gradle59
-rw-r--r--StoneIsland/platforms/android/cordova/.jshintrc10
-rw-r--r--StoneIsland/platforms/android/cordova/Api.js415
-rwxr-xr-xStoneIsland/platforms/android/cordova/build45
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/build.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/check_reqs.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/clean39
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/clean.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/defaults.xml0
-rw-r--r--StoneIsland/platforms/android/cordova/lib/Adb.js105
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidManifest.js161
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidProject.js210
-rw-r--r--StoneIsland/platforms/android/cordova/lib/AndroidStudio.js42
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/appinfo.js41
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/build.js590
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js156
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js147
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js266
-rw-r--r--StoneIsland/platforms/android/cordova/lib/builders/builders.js47
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/check_reqs.js87
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/device.js81
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/emulator.js339
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/exec.js68
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/install-device.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/install-emulator.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-devices15
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-devices.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-emulator-images14
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/list-started-emulators14
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/log.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/plugin-build.gradle17
-rw-r--r--StoneIsland/platforms/android/cordova/lib/pluginHandlers.js308
-rw-r--r--StoneIsland/platforms/android/cordova/lib/prepare.js431
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/retry.js6
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/run.js99
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/spawn.js50
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/lib/start-emulator.bat0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/log.bat0
-rw-r--r--StoneIsland/platforms/android/cordova/loggingHelper.js18
-rwxr-xr-xStoneIsland/platforms/android/cordova/run44
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/run.bat0
-rwxr-xr-xStoneIsland/platforms/android/cordova/version8
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/cordova/version.bat0
-rw-r--r--StoneIsland/platforms/android/google-services.json4
-rw-r--r--StoneIsland/platforms/android/phonegap-plugin-push/stoneisland-push.gradle21
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova-js-src/android/nativeapiprovider.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova-js-src/android/promptbasednativeapi.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova-js-src/exec.js20
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova-js-src/platform.js36
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova-js-src/plugin/android/app.js0
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/platform_www/cordova.js291
-rwxr-xr-xStoneIsland/platforms/android/platform_www/cordova_plugins.js13
-rw-r--r--StoneIsland/platforms/android/platform_www/plugins/phonegap-plugin-push/www/push.js329
-rwxr-xr-xStoneIsland/platforms/android/project.properties6
-rwxr-xr-xStoneIsland/platforms/android/res/values/strings.xml1
-rw-r--r--[-rwxr-xr-x]StoneIsland/platforms/android/res/xml/config.xml5
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/BackgroundActionButtonHandler.java41
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java802
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/PermissionUtils.java55
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java72
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java120
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java27
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java458
-rw-r--r--StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java38
124 files changed, 6640 insertions, 1316 deletions
diff --git a/StoneIsland/platforms/android/AndroidManifest.xml b/StoneIsland/platforms/android/AndroidManifest.xml
index da0336cf..b8feb984 100755
--- a/StoneIsland/platforms/android/AndroidManifest.xml
+++ b/StoneIsland/platforms/android/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
-<manifest android:hardwareAccelerated="true" android:versionCode="700" android:versionName="0.7.0" package="us.okfoc.stoneisland" xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest android:hardwareAccelerated="true" android:versionCode="801" android:versionName="0.8.1" package="us.okfoc.stoneisland" xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -43,6 +43,25 @@
<category android:name="us.okfoc.stoneisland" />
</intent-filter>
</receiver>
+ <activity android:exported="true" android:name="com.adobe.phonegap.push.PushHandlerActivity" android:permission="${applicationId}.permission.PushHandlerActivity" />
+ <receiver android:name="com.adobe.phonegap.push.BackgroundActionButtonHandler" />
+ <receiver android:exported="true" android:name="com.google.android.gms.gcm.GcmReceiver" android:permission="com.google.android.c2dm.permission.SEND">
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <category android:name="${applicationId}" />
+ </intent-filter>
+ </receiver>
+ <service android:exported="false" android:name="com.adobe.phonegap.push.GCMIntentService">
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ </intent-filter>
+ </service>
+ <service android:exported="false" android:name="com.adobe.phonegap.push.PushInstanceIDListenerService">
+ <intent-filter>
+ <action android:name="com.google.android.gms.iid.InstanceID" />
+ </intent-filter>
+ </service>
+ <service android:exported="false" android:name="com.adobe.phonegap.push.RegistrationIntentService" />
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -57,4 +76,8 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
+ <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
+ <uses-permission android:name="${applicationId}.permission.PushHandlerActivity" />
+ <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" />
+ <permission android:name="${applicationId}.permission.PushHandlerActivity" android:protectionLevel="signature" />
</manifest>
diff --git a/StoneIsland/platforms/android/CordovaLib/build.gradle b/StoneIsland/platforms/android/CordovaLib/build.gradle
index 2565633f..b580e13f 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/build.gradle
+++ b/StoneIsland/platforms/android/CordovaLib/build.gradle
@@ -21,28 +21,22 @@
buildscript {
repositories {
mavenCentral()
+ jcenter();
}
- // Switch the Android Gradle plugin version requirement depending on the
- // installed version of Gradle. This dependency is documented at
- // http://tools.android.com/tech-docs/new-build-system/version-compatibility
- // and https://issues.apache.org/jira/browse/CB-8143
- if (gradle.gradleVersion >= "2.2") {
- dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0+'
- }
- } else if (gradle.gradleVersion >= "2.1") {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.14.0+'
- }
- } else {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.12.0+'
- }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.2.1'
}
+
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
+
+ext {
+ apply from: 'cordova.gradle'
+ cdvCompileSdkVersion = privateHelpers.getProjectTarget()
+ cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
+}
android {
compileSdkVersion cdvCompileSdkVersion
diff --git a/StoneIsland/platforms/android/CordovaLib/cordova.gradle b/StoneIsland/platforms/android/CordovaLib/cordova.gradle
index 6e89c4c2..21a01bb5 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/cordova.gradle
+++ b/StoneIsland/platforms/android/CordovaLib/cordova.gradle
@@ -61,7 +61,7 @@ String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
highestBuildToolsVersion
} else {
throw new RuntimeException(
- "No installed build tools found. Please install the Android build tools version " +
+ "No installed build tools found. Install the Android build tools version " +
minBuildToolsVersion + " or higher.")
}
}
@@ -125,7 +125,15 @@ def doExtractIntFromManifest(name) {
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
def matcher = pattern.matcher(manifestFile.getText())
matcher.find()
- return Integer.parseInt(matcher.group(1))
+ return new BigInteger(matcher.group(1))
+}
+
+def doExtractStringFromManifest(name) {
+ def manifestFile = file(android.sourceSets.main.manifest.srcFile)
+ def pattern = Pattern.compile(name + "=\"(\\S+)\"")
+ def matcher = pattern.matcher(manifestFile.getText())
+ matcher.find()
+ return matcher.group(1)
}
def doPromptForPassword(msg) {
@@ -179,6 +187,7 @@ ext {
privateHelpers.getProjectTarget = { doGetProjectTarget() }
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
+ privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
diff --git a/StoneIsland/platforms/android/CordovaLib/project.properties b/StoneIsland/platforms/android/CordovaLib/project.properties
index 40ae82c1..df3c73c4 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/project.properties
+++ b/StoneIsland/platforms/android/CordovaLib/project.properties
@@ -10,7 +10,7 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
-target=android-22
+target=android-25
apk-configurations=
renderscript.opt.level=O0
android.library=true
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java
index d3a231a0..d3a231a0 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/AuthenticationToken.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java
index 446c37d9..43363869 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackContext.java
@@ -20,8 +20,6 @@ package org.apache.cordova;
import org.json.JSONArray;
-import android.util.Log;
-
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONObject;
@@ -31,22 +29,22 @@ public class CallbackContext {
private String callbackId;
private CordovaWebView webView;
- private boolean finished;
+ protected boolean finished;
private int changingThreads;
public CallbackContext(String callbackId, CordovaWebView webView) {
this.callbackId = callbackId;
this.webView = webView;
}
-
+
public boolean isFinished() {
return finished;
}
-
+
public boolean isChangingThreads() {
return changingThreads > 0;
}
-
+
public String getCallbackId() {
return callbackId;
}
@@ -54,7 +52,7 @@ public class CallbackContext {
public void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (finished) {
- Log.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
+ LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = !pluginResult.getKeepCallback();
@@ -98,7 +96,7 @@ public class CallbackContext {
public void success(byte[] message) {
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
}
-
+
/**
* Helper for success callbacks that just returns the Status.OK by default
*
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackMap.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackMap.java
new file mode 100644
index 00000000..050daa01
--- /dev/null
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CallbackMap.java
@@ -0,0 +1,65 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova;
+
+import android.util.Pair;
+import android.util.SparseArray;
+
+/**
+ * Provides a collection that maps unique request codes to CordovaPlugins and Integers.
+ * Used to ensure that when plugins make requests for runtime permissions, those requests do not
+ * collide with requests from other plugins that use the same request code value.
+ */
+public class CallbackMap {
+ private int currentCallbackId = 0;
+ private SparseArray<Pair<CordovaPlugin, Integer>> callbacks;
+
+ public CallbackMap() {
+ this.callbacks = new SparseArray<Pair<CordovaPlugin, Integer>>();
+ }
+
+ /**
+ * Stores a CordovaPlugin and request code and returns a new unique request code to use
+ * in a permission request.
+ *
+ * @param receiver The plugin that is making the request
+ * @param requestCode The original request code used by the plugin
+ * @return A unique request code that can be used to retrieve this callback
+ * with getAndRemoveCallback()
+ */
+ public synchronized int registerCallback(CordovaPlugin receiver, int requestCode) {
+ int mappedId = this.currentCallbackId++;
+ callbacks.put(mappedId, new Pair<CordovaPlugin, Integer>(receiver, requestCode));
+ return mappedId;
+ }
+
+ /**
+ * Retrieves and removes a callback stored in the map using the mapped request code
+ * obtained from registerCallback()
+ *
+ * @param mappedId The request code obtained from registerCallback()
+ * @return The CordovaPlugin and orignal request code that correspond to the
+ * given mappedCode
+ */
+ public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
+ Pair<CordovaPlugin, Integer> callback = callbacks.get(mappedId);
+ callbacks.remove(mappedId);
+ return callback;
+ }
+}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Config.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Config.java
index 048960bf..07397959 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Config.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Config.java
@@ -22,7 +22,6 @@ package org.apache.cordova;
import java.util.List;
import android.app.Activity;
-import android.util.Log;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
public class Config {
@@ -61,7 +60,7 @@ public class Config {
public static List<PluginEntry> getPluginEntries() {
return parser.getPluginEntries();
}
-
+
public static CordovaPreferences getPreferences() {
return parser.getPreferences();
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ConfigXmlParser.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ConfigXmlParser.java
index 01a97f2d..01a97f2d 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ConfigXmlParser.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ConfigXmlParser.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java
index 5c3bc508..85eeb53a 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaActivity.java
@@ -26,6 +26,7 @@ import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
+import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
@@ -33,7 +34,6 @@ import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -49,7 +49,7 @@ import android.widget.FrameLayout;
* html file that contains the application.
*
* As an example:
- *
+ *
* <pre>
* package org.apache.cordova.examples;
*
@@ -66,8 +66,8 @@ import android.widget.FrameLayout;
* }
* }
* </pre>
- *
- * Cordova xml configuration: Cordova uses a configuration file at
+ *
+ * Cordova xml configuration: Cordova uses a configuration file at
* res/xml/config.xml to specify its settings. See "The config.xml File"
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
* for the configuration. The use of the set*Property() methods is
@@ -103,31 +103,31 @@ public class CordovaActivity extends Activity {
*/
@Override
public void onCreate(Bundle savedInstanceState) {
+ // need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
+ loadConfig();
+
+ String logLevel = preferences.getString("loglevel", "ERROR");
+ LOG.setLogLevel(logLevel);
+
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
LOG.d(TAG, "CordovaActivity.onCreate()");
- // need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
- loadConfig();
- if(!preferences.getBoolean("ShowTitle", false))
- {
+ if (!preferences.getBoolean("ShowTitle", false)) {
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
}
- if(preferences.getBoolean("SetFullscreen", false))
- {
- Log.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
+ if (preferences.getBoolean("SetFullscreen", false)) {
+ LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
preferences.set("Fullscreen", true);
}
- if(preferences.getBoolean("Fullscreen", false))
- {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
- {
+ if (preferences.getBoolean("Fullscreen", false)) {
+ // NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
+ // (as was the case in previous cordova versions)
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) && !preferences.getBoolean("FullscreenNotImmersive", false)) {
immersiveMode = true;
- }
- else
- {
+ } else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
@@ -137,12 +137,11 @@ public class CordovaActivity extends Activity {
super.onCreate(savedInstanceState);
cordovaInterface = makeCordovaInterface();
- if(savedInstanceState != null)
- {
+ if (savedInstanceState != null) {
cordovaInterface.restoreInstanceState(savedInstanceState);
}
}
-
+
protected void init() {
appView = makeWebView();
createViews();
@@ -181,9 +180,14 @@ public class CordovaActivity extends Activity {
setContentView(appView.getView());
if (preferences.contains("BackgroundColor")) {
- int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
- // Background of activity:
- appView.getView().setBackgroundColor(backgroundColor);
+ try {
+ int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
+ // Background of activity:
+ appView.getView().setBackgroundColor(backgroundColor);
+ }
+ catch (NumberFormatException e){
+ e.printStackTrace();
+ }
}
appView.getView().requestFocusFromTouch();
@@ -191,7 +195,7 @@ public class CordovaActivity extends Activity {
/**
* Construct the default web view object.
- *
+ * <p/>
* Override this to customize the webview that is used.
*/
protected CordovaWebView makeWebView() {
@@ -244,13 +248,13 @@ public class CordovaActivity extends Activity {
/**
* Called when the activity receives a new intent
- **/
+ */
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//Forward to plugins
if (this.appView != null)
- this.appView.onNewIntent(intent);
+ this.appView.onNewIntent(intent);
}
/**
@@ -260,7 +264,7 @@ public class CordovaActivity extends Activity {
protected void onResume() {
super.onResume();
LOG.d(TAG, "Resumed the activity.");
-
+
if (this.appView == null) {
return;
}
@@ -320,16 +324,17 @@ public class CordovaActivity extends Activity {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && immersiveMode) {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
}
}
+ @SuppressLint("NewApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
@@ -341,10 +346,10 @@ public class CordovaActivity extends Activity {
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
- * @param requestCode The request code originally supplied to startActivityForResult(),
- * allowing you to identify who this result came from.
- * @param resultCode The integer result code returned by the child activity through its setResult().
- * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
+ * @param requestCode The request code originally supplied to startActivityForResult(),
+ * allowing you to identify who this result came from.
+ * @param resultCode The integer result code returned by the child activity through its setResult().
+ * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
@@ -357,9 +362,9 @@ public class CordovaActivity extends Activity {
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
- * @param errorCode The error code corresponding to an ERROR_* value.
- * @param description A String describing the error.
- * @param failingUrl The url that failed to load.
+ * @param errorCode The error code corresponding to an ERROR_* value.
+ * @param description A String describing the error.
+ * @param failingUrl The url that failed to load.
*/
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
final CordovaActivity me = this;
@@ -448,9 +453,9 @@ public class CordovaActivity extends Activity {
/**
* Called when a message is sent to plugin.
*
- * @param id The message id
- * @param data The message data
- * @return Object or null
+ * @param id The message id
+ * @param data The message data
+ * @return Object or null
*/
public Object onMessage(String id, Object data) {
if ("onReceivedError".equals(id)) {
@@ -466,8 +471,7 @@ public class CordovaActivity extends Activity {
return null;
}
- protected void onSaveInstanceState(Bundle outState)
- {
+ protected void onSaveInstanceState(Bundle outState) {
cordovaInterface.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@@ -475,7 +479,7 @@ public class CordovaActivity extends Activity {
/**
* Called by the system when the device configuration changes while your activity is running.
*
- * @param newConfig The new device configuration
+ * @param newConfig The new device configuration
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -488,4 +492,27 @@ public class CordovaActivity extends Activity {
pm.onConfigurationChanged(newConfig);
}
}
+
+ /**
+ * Called by the system when the user grants permissions
+ *
+ * @param requestCode
+ * @param permissions
+ * @param grantResults
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[],
+ int[] grantResults) {
+ try
+ {
+ cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
+ }
+ catch (JSONException e)
+ {
+ LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
+ e.printStackTrace();
+ }
+
+ }
+
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java
index d40d26eb..d40d26eb 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaArgs.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
index 7bc4a552..9459a113 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
@@ -23,8 +23,6 @@ import java.security.SecureRandom;
import org.json.JSONArray;
import org.json.JSONException;
-import android.util.Log;
-
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
@@ -87,15 +85,15 @@ public class CordovaBridge {
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
if (!jsMessageQueue.isBridgeEnabled()) {
if (bridgeSecret == -1) {
- Log.d(LOG_TAG, action + " call made before bridge was enabled.");
+ LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
} else {
- Log.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
+ LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
}
return false;
}
// Bridge secret wrong and bridge not due to it being from the previous page.
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
- Log.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
+ LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
clearBridgeSecret();
throw new IllegalAccessException();
}
@@ -120,7 +118,7 @@ public class CordovaBridge {
public void reset() {
jsMessageQueue.reset();
- clearBridgeSecret();
+ clearBridgeSecret();
}
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
@@ -141,7 +139,7 @@ public class CordovaBridge {
}
return "";
}
- // Sets the native->JS bridge mode.
+ // Sets the native->JS bridge mode.
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
try {
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
@@ -153,7 +151,7 @@ public class CordovaBridge {
}
return "";
}
- // Polling for JavaScript messages
+ // Polling for JavaScript messages
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
try {
@@ -175,7 +173,7 @@ public class CordovaBridge {
int secret = generateBridgeSecret();
return ""+secret;
} else {
- Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
+ LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
}
return "";
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
index 5dd0ecae..5dd0ecae 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
index a219c992..a219c992 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
index 724381e2..724381e2 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
index 59ed4864..3b8468f3 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
@@ -69,4 +69,20 @@ public interface CordovaInterface {
* Returns a shared thread pool that can be used for background tasks.
*/
public ExecutorService getThreadPool();
+
+ /**
+ * Sends a permission request to the activity for one permission.
+ */
+ public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
+
+ /**
+ * Sends a permission request to the activity for a group of permissions
+ */
+ public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
+
+ /**
+ * Check for a permission. Returns true if the permission is granted, false otherwise.
+ */
+ public boolean hasPermission(String permission);
+
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
index e35a181d..71dcb782 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
@@ -21,8 +21,13 @@ package org.apache.cordova;
import android.app.Activity;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
-import android.util.Log;
+import android.util.Pair;
+
+import org.json.JSONException;
+import org.json.JSONObject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -37,9 +42,12 @@ public class CordovaInterfaceImpl implements CordovaInterface {
protected PluginManager pluginManager;
protected ActivityResultHolder savedResult;
+ protected CallbackMap permissionResultCallbacks;
protected CordovaPlugin activityResultCallback;
protected String initCallbackService;
protected int activityResultRequestCode;
+ protected boolean activityWasDestroyed = false;
+ protected Bundle savedPluginState;
public CordovaInterfaceImpl(Activity activity) {
this(activity, Executors.newCachedThreadPool());
@@ -48,6 +56,7 @@ public class CordovaInterfaceImpl implements CordovaInterface {
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
this.activity = activity;
this.threadPool = threadPool;
+ this.permissionResultCallbacks = new CallbackMap();
}
@Override
@@ -89,12 +98,31 @@ public class CordovaInterfaceImpl implements CordovaInterface {
}
/**
- * Dispatches any pending onActivityResult callbacks.
+ * Dispatches any pending onActivityResult callbacks and sends the resume event if the
+ * Activity was destroyed by the OS.
*/
public void onCordovaInit(PluginManager pluginManager) {
this.pluginManager = pluginManager;
if (savedResult != null) {
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
+ } else if(activityWasDestroyed) {
+ // If there was no Activity result, we still need to send out the resume event if the
+ // Activity was destroyed by the OS
+ activityWasDestroyed = false;
+ if(pluginManager != null)
+ {
+ CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
+ if(appPlugin != null) {
+ JSONObject obj = new JSONObject();
+ try {
+ obj.put("action", "resume");
+ } catch (JSONException e) {
+ LOG.e(TAG, "Failed to create event message", e);
+ }
+ appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
+ }
+ }
+
}
}
@@ -109,18 +137,22 @@ public class CordovaInterfaceImpl implements CordovaInterface {
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
if (pluginManager != null) {
callback = pluginManager.getPlugin(initCallbackService);
+ if(callback != null) {
+ callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
+ new ResumeCallback(callback.getServiceName(), pluginManager));
+ }
}
}
activityResultCallback = null;
if (callback != null) {
- Log.d(TAG, "Sending activity result to plugin");
+ LOG.d(TAG, "Sending activity result to plugin");
initCallbackService = null;
savedResult = null;
callback.onActivityResult(requestCode, resultCode, intent);
return true;
}
- Log.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!": "."));
+ LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
return false;
}
@@ -141,6 +173,10 @@ public class CordovaInterfaceImpl implements CordovaInterface {
String serviceName = activityResultCallback.getServiceName();
outState.putString("callbackService", serviceName);
}
+ if(pluginManager != null){
+ outState.putBundle("plugin", pluginManager.onSaveInstanceState());
+ }
+
}
/**
@@ -148,6 +184,8 @@ public class CordovaInterfaceImpl implements CordovaInterface {
*/
public void restoreInstanceState(Bundle savedInstanceState) {
initCallbackService = savedInstanceState.getString("callbackService");
+ savedPluginState = savedInstanceState.getBundle("plugin");
+ activityWasDestroyed = true;
}
private static class ActivityResultHolder {
@@ -161,4 +199,43 @@ public class CordovaInterfaceImpl implements CordovaInterface {
this.intent = intent;
}
}
+
+ /**
+ * Called by the system when the user grants permissions
+ *
+ * @param requestCode
+ * @param permissions
+ * @param grantResults
+ */
+ public void onRequestPermissionResult(int requestCode, String[] permissions,
+ int[] grantResults) throws JSONException {
+ Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
+ if(callback != null) {
+ callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
+ }
+ }
+
+ public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
+ String[] permissions = new String [1];
+ permissions[0] = permission;
+ requestPermissions(plugin, requestCode, permissions);
+ }
+
+ public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
+ int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
+ getActivity().requestPermissions(permissions, mappedRequestCode);
+ }
+
+ public boolean hasPermission(String permission)
+ {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ {
+ int result = activity.checkSelfPermission(permission);
+ return PackageManager.PERMISSION_GRANTED == result;
+ }
+ else
+ {
+ return true;
+ }
+ }
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
index 7cf8528f..41af1db7 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
@@ -26,8 +26,11 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -75,7 +78,7 @@ public class CordovaPlugin {
public String getServiceName() {
return serviceName;
}
-
+
/**
* Executes the request.
*
@@ -173,6 +176,29 @@ public class CordovaPlugin {
}
/**
+ * Called when the Activity is being destroyed (e.g. if a plugin calls out to an external
+ * Activity and the OS kills the CordovaActivity in the background). The plugin should save its
+ * state in this method only if it is awaiting the result of an external Activity and needs
+ * to preserve some information so as to handle that result; onRestoreStateForActivityResult()
+ * will only be called if the plugin is the recipient of an Activity result
+ *
+ * @return Bundle containing the state of the plugin or null if state does not need to be saved
+ */
+ public Bundle onSaveInstanceState() {
+ return null;
+ }
+
+ /**
+ * Called when a plugin is the recipient of an Activity result after the CordovaActivity has
+ * been destroyed. The Bundle will be the same as the one the plugin returned in
+ * onSaveInstanceState()
+ *
+ * @param state Bundle containing the state of the plugin
+ * @param callbackContext Replacement Context to return the plugin result to
+ */
+ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
+
+ /**
* Called when a message is sent to plugin.
*
* @param id The message id
@@ -321,7 +347,7 @@ public class CordovaPlugin {
*/
public void onReset() {
}
-
+
/**
* Called when the system received an HTTP authentication request. Plugin can use
* the supplied HttpAuthHandler to process this auth challenge.
@@ -330,14 +356,14 @@ public class CordovaPlugin {
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
- *
+ *
* @return Returns True if plugin will resolve this auth challenge, otherwise False
- *
+ *
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
return false;
}
-
+
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
@@ -359,4 +385,38 @@ public class CordovaPlugin {
*/
public void onConfigurationChanged(Configuration newConfig) {
}
+
+ /**
+ * Called by the Plugin Manager when we need to actually request permissions
+ *
+ * @param requestCode Passed to the activity to track the request
+ *
+ * @return Returns the permission that was stored in the plugin
+ */
+
+ public void requestPermissions(int requestCode) {
+ }
+
+ /*
+ * Called by the WebView implementation to check for geolocation permissions, can be used
+ * by other Java methods in the event that a plugin is using this as a dependency.
+ *
+ * @return Returns true if the plugin has all the permissions it needs to operate.
+ */
+
+ public boolean hasPermisssion() {
+ return true;
+ }
+
+ /**
+ * Called by the system when the user grants permissions
+ *
+ * @param requestCode
+ * @param permissions
+ * @param grantResults
+ */
+ public void onRequestPermissionResult(int requestCode, String[] permissions,
+ int[] grantResults) throws JSONException {
+
+ }
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
index 4dbc93e6..4dbc93e6 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
index e725e255..e725e255 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
index ba58f9a0..2eebd0d3 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
@@ -31,7 +31,7 @@ import android.webkit.WebChromeClient.CustomViewCallback;
* are not expected to implement it.
*/
public interface CordovaWebView {
- public static final String CORDOVA_VERSION = "4.1.1";
+ public static final String CORDOVA_VERSION = "6.1.2";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
index dd84ab11..c8e5a55d 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
@@ -20,9 +20,10 @@ package org.apache.cordova;
import android.view.KeyEvent;
import android.view.View;
+import android.webkit.ValueCallback;
/**
- * Interfcae for all Cordova engines.
+ * Interface for all Cordova engines.
* No methods will be added to this class (in order to be compatible with existing engines).
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
*/
@@ -58,6 +59,9 @@ public interface CordovaWebViewEngine {
/** Clean up all resources associated with the WebView. */
void destroy();
+ /** Add the evaulate Javascript method **/
+ void evaluateJavascript(String js, ValueCallback<String> callback);
+
/**
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java
index 06da55e1..85a0b5f5 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java
@@ -21,7 +21,6 @@ package org.apache.cordova;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -135,6 +134,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
if (recreatePlugins) {
// Don't re-initialize on first load.
if (loadedUrl != null) {
+ appPlugin = null;
pluginManager.init();
}
loadedUrl = url;
@@ -244,7 +244,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
@Deprecated
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
- Log.d(TAG, "showing Custom View");
+ LOG.d(TAG, "showing Custom View");
// if a view already exists then immediately terminate the new one
if (mCustomView != null) {
callback.onCustomViewHidden();
@@ -275,7 +275,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
public void hideCustomView() {
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
if (mCustomView == null) return;
- Log.d(TAG, "Hiding Custom View");
+ LOG.d(TAG, "Hiding Custom View");
// Hide the custom view.
mCustomView.setVisibility(View.GONE);
@@ -354,6 +354,7 @@ public class CordovaWebViewImpl implements CordovaWebView {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_BACK:
+ case KeyEvent.KEYCODE_MENU:
// TODO: Why are search and menu buttons handled separately?
if (override) {
boundKeyCodes.add(keyCode);
@@ -445,7 +446,10 @@ public class CordovaWebViewImpl implements CordovaWebView {
// Resume JavaScript timers. This affects all webviews within the app!
engine.setPaused(false);
this.pluginManager.onResume(keepRunning);
- // To be the same as other platforms, fire this event only when resumed after a "pause".
+
+ // In order to match the behavior of the other platforms, we only send onResume after an
+ // onPause has occurred. The resume event might still be sent if the Activity was killed
+ // while waiting for the result of an external Activity once the result is obtained
if (hasPausedEver) {
sendJavascriptEvent("resume");
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java
index 000717a2..e384f8d7 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java
@@ -19,10 +19,6 @@
package org.apache.cordova;
-import org.apache.cordova.CallbackContext;
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.LOG;
-import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -34,17 +30,20 @@ import android.content.IntentFilter;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
+import java.lang.reflect.Field;
import java.util.HashMap;
/**
* This class exposes methods in Cordova that can be called from JavaScript.
*/
-class CoreAndroid extends CordovaPlugin {
+public class CoreAndroid extends CordovaPlugin {
public static final String PLUGIN_NAME = "CoreAndroid";
protected static final String TAG = "CordovaApp";
private BroadcastReceiver telephonyReceiver;
private CallbackContext messageChannel;
+ private PluginResult pendingResume;
+ private final Object messageChannelLock = new Object();
/**
* Send an event to be fired on the Javascript side.
@@ -112,7 +111,13 @@ class CoreAndroid extends CordovaPlugin {
this.exitApp();
}
else if (action.equals("messageChannel")) {
- messageChannel = callbackContext;
+ synchronized(messageChannelLock) {
+ messageChannel = callbackContext;
+ if (pendingResume != null) {
+ sendEventMessage(pendingResume);
+ pendingResume = null;
+ }
+ }
return true;
}
@@ -248,6 +253,9 @@ class CoreAndroid extends CordovaPlugin {
else if (button.equals("volumedown")) {
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
}
+ else if (button.equals("menubutton")) {
+ webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
+ }
}
/**
@@ -313,10 +321,13 @@ class CoreAndroid extends CordovaPlugin {
} catch (JSONException e) {
LOG.e(TAG, "Failed to create event message", e);
}
- PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
- pluginResult.setKeepCallback(true);
+ sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
+ }
+
+ private void sendEventMessage(PluginResult payload) {
+ payload.setKeepCallback(true);
if (messageChannel != null) {
- messageChannel.sendPluginResult(pluginResult);
+ messageChannel.sendPluginResult(payload);
}
}
@@ -328,4 +339,52 @@ class CoreAndroid extends CordovaPlugin {
{
webView.getContext().unregisterReceiver(this.telephonyReceiver);
}
+
+ /**
+ * Used to send the resume event in the case that the Activity is destroyed by the OS
+ *
+ * @param resumeEvent PluginResult containing the payload for the resume event to be fired
+ */
+ public void sendResumeEvent(PluginResult resumeEvent) {
+ // This operation must be synchronized because plugin results that trigger resume
+ // events can be processed asynchronously
+ synchronized(messageChannelLock) {
+ if (messageChannel != null) {
+ sendEventMessage(resumeEvent);
+ } else {
+ // Might get called before the page loads, so we need to store it until the
+ // messageChannel gets created
+ this.pendingResume = resumeEvent;
+ }
+ }
+ }
+
+ /*
+ * This needs to be implemented if you wish to use the Camera Plugin or other plugins
+ * that read the Build Configuration.
+ *
+ * Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
+ * StackOverflow. This is annoying as hell!
+ *
+ */
+
+ public static Object getBuildConfigValue(Context ctx, String key)
+ {
+ try
+ {
+ Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
+ Field field = clazz.getField(key);
+ return field.get(null);
+ } catch (ClassNotFoundException e) {
+ LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
+ e.printStackTrace();
+ } catch (NoSuchFieldException e) {
+ LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
+ } catch (IllegalAccessException e) {
+ LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
+ e.printStackTrace();
+ }
+
+ return null;
+ }
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java
index acc65c6f..acc65c6f 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java
index 455d2f9d..455d2f9d 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java
index e776194f..e776194f 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java
index c55818ac..c55818ac 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/LOG.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/LOG.java
index 3dd1a754..9fe7a7df 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/LOG.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/LOG.java
@@ -158,6 +158,16 @@ public class LOG {
* Warning log message.
*
* @param tag
+ * @param e
+ */
+ public static void w(String tag, Throwable e) {
+ if (LOG.WARN >= LOGLEVEL) Log.w(tag, e);
+ }
+
+ /**
+ * Warning log message.
+ *
+ * @param tag
* @param s
* @param e
*/
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java
index a05e8b81..61d04f17 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -21,8 +21,6 @@ package org.apache.cordova;
import java.util.ArrayList;
import java.util.LinkedList;
-import android.util.Log;
-
/**
* Holds the list of messages to be sent to the WebView.
*/
@@ -42,13 +40,13 @@ public class NativeToJsMessageQueue {
// This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
-
+
/**
* When true, the active listener is not fired upon enqueue. When set to false,
- * the active listener will be fired if the queue is non-empty.
+ * the active listener will be fired if the queue is non-empty.
*/
private boolean paused;
-
+
/**
* The list of JavaScript statements to be sent to JavaScript.
*/
@@ -58,7 +56,7 @@ public class NativeToJsMessageQueue {
* The array of listeners that can be used to send messages to JS.
*/
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
-
+
/**
* When null, the bridge is disabled. This occurs during page transitions.
* When disabled, all callbacks are dropped since they are assumed to be
@@ -83,11 +81,11 @@ public class NativeToJsMessageQueue {
*/
public void setBridgeMode(int value) {
if (value < -1 || value >= bridgeModes.size()) {
- Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
+ LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else {
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
if (newMode != activeBridgeMode) {
- Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
+ LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
synchronized (this) {
activeBridgeMode = newMode;
if (newMode != null) {
@@ -100,7 +98,7 @@ public class NativeToJsMessageQueue {
}
}
}
-
+
/**
* Clears all messages and resets to the default bridge mode.
*/
@@ -114,16 +112,16 @@ public class NativeToJsMessageQueue {
private int calculatePackedMessageLength(JsMessage message) {
int messageLen = message.calculateEncodedLength();
String messageLenStr = String.valueOf(messageLen);
- return messageLenStr.length() + messageLen + 1;
+ return messageLenStr.length() + messageLen + 1;
}
-
+
private void packMessage(JsMessage message, StringBuilder sb) {
int len = message.calculateEncodedLength();
sb.append(len)
.append(' ');
message.encodeAsMessage(sb);
}
-
+
/**
* Combines and returns queued messages combined into a single string.
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
@@ -154,7 +152,7 @@ public class NativeToJsMessageQueue {
JsMessage message = queue.removeFirst();
packMessage(message, sb);
}
-
+
if (!queue.isEmpty()) {
// Attach a char to indicate that there are more messages pending.
sb.append('*');
@@ -163,7 +161,7 @@ public class NativeToJsMessageQueue {
return ret;
}
}
-
+
/**
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
*/
@@ -185,7 +183,7 @@ public class NativeToJsMessageQueue {
}
boolean willSendAllMessages = numMessagesToSend == queue.size();
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
- // Wrap each statement in a try/finally so that if one throws it does
+ // Wrap each statement in a try/finally so that if one throws it does
// not affect the next.
for (int i = 0; i < numMessagesToSend; ++i) {
JsMessage message = queue.removeFirst();
@@ -206,7 +204,7 @@ public class NativeToJsMessageQueue {
String ret = sb.toString();
return ret;
}
- }
+ }
/**
* Add a JavaScript statement to the list.
@@ -220,7 +218,7 @@ public class NativeToJsMessageQueue {
*/
public void addPluginResult(PluginResult result, String callbackId) {
if (callbackId == null) {
- Log.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
+ LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
return;
}
// Don't send anything if there is no result and there is no need to
@@ -243,7 +241,7 @@ public class NativeToJsMessageQueue {
private void enqueueMessage(JsMessage message) {
synchronized (this) {
if (activeBridgeMode == null) {
- Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
+ LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
return;
}
queue.add(message);
@@ -257,7 +255,7 @@ public class NativeToJsMessageQueue {
if (paused && value) {
// This should never happen. If a use-case for it comes up, we should
// change pause to be a counter.
- Log.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
+ LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
}
paused = value;
if (!value) {
@@ -265,7 +263,7 @@ public class NativeToJsMessageQueue {
if (!queue.isEmpty() && activeBridgeMode != null) {
activeBridgeMode.onNativeToJsMessageAvailable(this);
}
- }
+ }
}
}
@@ -351,6 +349,31 @@ public class NativeToJsMessageQueue {
}
}
+ /** Uses webView.evaluateJavascript to execute messages. */
+ public static class EvalBridgeMode extends BridgeMode {
+ private final CordovaWebViewEngine engine;
+ private final CordovaInterface cordova;
+
+ public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
+ this.engine = engine;
+ this.cordova = cordova;
+ }
+
+ @Override
+ public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ String js = queue.popAndEncodeAsJs();
+ if (js != null) {
+ engine.evaluateJavascript(js, null);
+ }
+ }
+ });
+ }
+ }
+
+
+
private static class JsMessage {
final String jsPayloadOrCallbackId;
final PluginResult pluginResult;
@@ -368,7 +391,7 @@ public class NativeToJsMessageQueue {
jsPayloadOrCallbackId = callbackId;
this.pluginResult = pluginResult;
}
-
+
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
@@ -395,7 +418,7 @@ public class NativeToJsMessageQueue {
return pluginResult.getMessage().length();
}
}
-
+
int calculateEncodedLength() {
if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1;
@@ -424,7 +447,7 @@ public class NativeToJsMessageQueue {
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
sb.append('S');
sb.append(pluginResult.getMessage());
- break;
+ break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
sb.append('A');
sb.append(pluginResult.getMessage());
@@ -443,7 +466,7 @@ public class NativeToJsMessageQueue {
sb.append(pluginResult.getMessage()); // [ or {
}
}
-
+
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginManager.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginManager.java
index a541e770..c9576a6c 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginManager.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginManager.java
@@ -26,8 +26,8 @@ import org.json.JSONException;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Debug;
-import android.util.Log;
/**
* PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -47,6 +47,8 @@ public class PluginManager {
private final CordovaWebView app;
private boolean isInitialized;
+ private CordovaPlugin permissionRequester;
+
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
this.ctx = cordova;
this.app = cordovaWebView;
@@ -119,7 +121,7 @@ public class PluginManager {
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
CordovaPlugin plugin = getPlugin(service);
if (plugin == null) {
- Log.d(TAG, "exec() call to unknown plugin: " + service);
+ LOG.d(TAG, "exec() call to unknown plugin: " + service);
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
app.sendPluginResult(cr, callbackId);
return;
@@ -131,7 +133,7 @@ public class PluginManager {
long duration = System.currentTimeMillis() - pluginStartTime;
if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
- Log.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
+ LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
}
if (!wasValidAction) {
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
@@ -141,7 +143,7 @@ public class PluginManager {
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
callbackContext.sendPluginResult(cr);
} catch (Exception e) {
- Log.e(TAG, "Uncaught exception from plugin", e);
+ LOG.e(TAG, "Uncaught exception from plugin", e);
callbackContext.error(e.getMessage());
}
}
@@ -219,9 +221,9 @@ public class PluginManager {
* @param handler The HttpAuthHandler used to set the WebView's response
* @param host The host requiring authentication
* @param realm The realm for which authentication is required
- *
+ *
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
- *
+ *
*/
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
for (CordovaPlugin plugin : this.pluginMap.values()) {
@@ -231,7 +233,7 @@ public class PluginManager {
}
return false;
}
-
+
/**
* Called when he system received an SSL client certificate request. Plugin can use
* the supplied ClientCertRequest to process this certificate challenge.
@@ -508,4 +510,17 @@ public class PluginManager {
}
}
}
+
+ public Bundle onSaveInstanceState() {
+ Bundle state = new Bundle();
+ for (CordovaPlugin plugin : this.pluginMap.values()) {
+ if (plugin != null) {
+ Bundle pluginState = plugin.onSaveInstanceState();
+ if(pluginState != null) {
+ state.putBundle(plugin.getServiceName(), pluginState);
+ }
+ }
+ }
+ return state;
+ }
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginResult.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginResult.java
index 2b3ac72e..2b3ac72e 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginResult.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/PluginResult.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ResumeCallback.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ResumeCallback.java
new file mode 100644
index 00000000..49a43b5d
--- /dev/null
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/ResumeCallback.java
@@ -0,0 +1,76 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova;
+
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResumeCallback extends CallbackContext {
+ private final String TAG = "CordovaResumeCallback";
+ private String serviceName;
+ private PluginManager pluginManager;
+
+ public ResumeCallback(String serviceName, PluginManager pluginManager) {
+ super("resumecallback", null);
+ this.serviceName = serviceName;
+ this.pluginManager = pluginManager;
+ }
+
+ @Override
+ public void sendPluginResult(PluginResult pluginResult) {
+ synchronized (this) {
+ if (finished) {
+ LOG.w(TAG, serviceName + " attempted to send a second callback to ResumeCallback\nResult was: " + pluginResult.getMessage());
+ return;
+ } else {
+ finished = true;
+ }
+ }
+
+ JSONObject event = new JSONObject();
+ JSONObject pluginResultObject = new JSONObject();
+
+ try {
+ pluginResultObject.put("pluginServiceName", this.serviceName);
+ pluginResultObject.put("pluginStatus", PluginResult.StatusMessages[pluginResult.getStatus()]);
+
+ event.put("action", "resume");
+ event.put("pendingResult", pluginResultObject);
+ } catch (JSONException e) {
+ LOG.e(TAG, "Unable to create resume object for Activity Result");
+ }
+
+ PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event);
+
+ // We send a list of results to the js so that we don't have to decode
+ // the PluginResult passed to this CallbackContext into JSON twice.
+ // The results are combined into an event payload before the event is
+ // fired on the js side of things (see platform.js)
+ List<PluginResult> result = new ArrayList<PluginResult>();
+ result.add(eventResult);
+ result.add(pluginResult);
+
+ CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
+ appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, result));
+ }
+}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Whitelist.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Whitelist.java
index d0f823c3..d0f823c3 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Whitelist.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/Whitelist.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemCookieManager.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemCookieManager.java
index ae55dfee..acf795fa 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemCookieManager.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemCookieManager.java
@@ -19,6 +19,7 @@
package org.apache.cordova.engine;
+import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.CookieManager;
import android.webkit.WebView;
@@ -30,10 +31,15 @@ class SystemCookieManager implements ICordovaCookieManager {
protected final WebView webView;
private final CookieManager cookieManager;
+ //Added because lint can't see the conditional RIGHT ABOVE this
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SystemCookieManager(WebView webview) {
webView = webview;
cookieManager = CookieManager.getInstance();
+ //REALLY? Nobody has seen this UNTIL NOW?
+ cookieManager.setAcceptFileSchemeCookies(true);
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, true);
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebChromeClient.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebChromeClient.java
index 3b5866c3..3ea5e576 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebChromeClient.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebChromeClient.java
@@ -21,11 +21,11 @@ package org.apache.cordova.engine;
import java.util.Arrays;
import android.annotation.TargetApi;
import android.app.Activity;
+import android.content.Context;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
@@ -61,15 +61,17 @@ public class SystemWebChromeClient extends WebChromeClient {
// the video progress view
private View mVideoProgressView;
-
+
private CordovaDialogsHelper dialogsHelper;
+ private Context appContext;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private View mCustomView;
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
- dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
+ appContext = parentEngine.webView.getContext();
+ dialogsHelper = new CordovaDialogsHelper(appContext);
}
/**
@@ -174,14 +176,23 @@ public class SystemWebChromeClient extends WebChromeClient {
/**
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
*
+ * This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
+ *
* @param origin
* @param callback
*/
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
+ //Get the plugin, it should be loaded
+ CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
+ if(geolocation != null && !geolocation.hasPermisssion())
+ {
+ geolocation.requestPermissions(0);
+ }
+
}
-
+
// API level 7 is required for this, see if we could lower this using something else
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
@@ -201,9 +212,9 @@ public class SystemWebChromeClient extends WebChromeClient {
*/
public View getVideoLoadingProgressView() {
- if (mVideoProgressView == null) {
+ if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
-
+
// create the linear layout
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
layout.setOrientation(LinearLayout.VERTICAL);
@@ -214,12 +225,12 @@ public class SystemWebChromeClient extends WebChromeClient {
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
- bar.setLayoutParams(barLayoutParams);
+ bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
-
+
mVideoProgressView = layout;
}
- return mVideoProgressView;
+ return mVideoProgressView;
}
// <input type=file> support:
@@ -228,11 +239,11 @@ public class SystemWebChromeClient extends WebChromeClient {
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
-
+
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
-
+
public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
@@ -242,7 +253,7 @@ public class SystemWebChromeClient extends WebChromeClient {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
- Log.d(LOG_TAG, "Receive file chooser URL: " + result);
+ LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
uploadMsg.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
@@ -257,12 +268,12 @@ public class SystemWebChromeClient extends WebChromeClient {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
- Log.d(LOG_TAG, "Receive file chooser URL: " + result);
+ LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
- Log.w("No activity found to handle file chooser intent.", e);
+ LOG.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
@@ -271,7 +282,7 @@ public class SystemWebChromeClient extends WebChromeClient {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onPermissionRequest(final PermissionRequest request) {
- Log.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
+ LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
request.grant(request.getResources());
}
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebView.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebView.java
index 01c2f000..01c2f000 100755..100644
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebView.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebView.java
diff --git a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebViewEngine.java b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebViewEngine.java
index 221a884c..0fa02767 100755
--- a/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebViewEngine.java
+++ b/StoneIsland/platforms/android/CordovaLib/src/org/apache/cordova/engine/SystemWebViewEngine.java
@@ -27,8 +27,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.os.Build;
-import android.util.Log;
import android.view.View;
+import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
@@ -40,6 +40,7 @@ import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.ICordovaCookieManager;
+import org.apache.cordova.LOG;
import org.apache.cordova.NativeToJsMessageQueue;
import org.apache.cordova.PluginManager;
@@ -116,7 +117,9 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
- bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
+ if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)
+ nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
+ bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
exposeJsInterface(webView, bridge);
}
@@ -135,7 +138,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
return webView;
}
- @SuppressLint("SetJavaScriptEnabled")
+ @SuppressLint({"NewApi", "SetJavaScriptEnabled"})
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
webView.setInitialScale(0);
@@ -145,32 +148,32 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
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);
+ 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");
+ 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");
+ 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");
+ 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.");
+ LOG.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
-
+
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
@@ -184,15 +187,15 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
-
-
+
+
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
enableRemoteDebugging();
}
-
+
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
@@ -200,13 +203,13 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
-
+
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
-
+
// Fix for CB-1405
// Google issue 4641
String defaultUserAgent = settings.getUserAgentString();
@@ -222,7 +225,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
}
}
// End CB-3360
-
+
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
@@ -242,18 +245,18 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
try {
WebView.setWebContentsDebuggingEnabled(true);
} catch (IllegalArgumentException e) {
- Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
+ LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
}
}
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
- Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
+ 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;
+ return;
}
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
@@ -312,8 +315,10 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
@Override
public void setPaused(boolean value) {
if (value) {
+ webView.onPause();
webView.pauseTimers();
} else {
+ webView.onResume();
webView.resumeTimers();
}
}
@@ -327,8 +332,19 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
try {
webView.getContext().unregisterReceiver(receiver);
} catch (Exception e) {
- Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
+ LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
+
+ @Override
+ public void evaluateJavascript(String js, ValueCallback<String> callback) {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ webView.evaluateJavascript(js, callback);
+ }
+ else
+ {
+ LOG.d(TAG, "This webview is using the old bridge");
+ }
+ }
}
diff --git a/StoneIsland/platforms/android/android.json b/StoneIsland/platforms/android/android.json
index 587c7239..d345c65d 100755
--- a/StoneIsland/platforms/android/android.json
+++ b/StoneIsland/platforms/android/android.json
@@ -51,6 +51,10 @@
{
"xml": "<feature name=\"Keyboard\"><param name=\"android-package\" value=\"io.ionic.keyboard.IonicKeyboard\" /><param name=\"onload\" value=\"true\" /></feature>",
"count": 1
+ },
+ {
+ "xml": "<feature name=\"PushNotification\"><param name=\"android-package\" value=\"com.adobe.phonegap.push.PushPlugin\" /></feature>",
+ "count": 1
}
]
}
@@ -73,20 +77,44 @@
{
"xml": "<receiver android:name=\"com.parse.GcmBroadcastReceiver\" android:permission=\"com.google.android.c2dm.permission.SEND\"><intent-filter><action android:name=\"com.google.android.c2dm.intent.RECEIVE\" /><action android:name=\"com.google.android.c2dm.intent.REGISTRATION\" /><category android:name=\"us.okfoc.stoneisland\" /></intent-filter></receiver>",
"count": 1
+ },
+ {
+ "xml": "<activity android:exported=\"true\" android:name=\"com.adobe.phonegap.push.PushHandlerActivity\" android:permission=\"${applicationId}.permission.PushHandlerActivity\" />",
+ "count": 1
+ },
+ {
+ "xml": "<receiver android:name=\"com.adobe.phonegap.push.BackgroundActionButtonHandler\" />",
+ "count": 1
+ },
+ {
+ "xml": "<receiver android:exported=\"true\" android:name=\"com.google.android.gms.gcm.GcmReceiver\" android:permission=\"com.google.android.c2dm.permission.SEND\"><intent-filter><action android:name=\"com.google.android.c2dm.intent.RECEIVE\" /><category android:name=\"${applicationId}\" /></intent-filter></receiver>",
+ "count": 1
+ },
+ {
+ "xml": "<service android:exported=\"false\" android:name=\"com.adobe.phonegap.push.GCMIntentService\"><intent-filter><action android:name=\"com.google.android.c2dm.intent.RECEIVE\" /></intent-filter></service>",
+ "count": 1
+ },
+ {
+ "xml": "<service android:exported=\"false\" android:name=\"com.adobe.phonegap.push.PushInstanceIDListenerService\"><intent-filter><action android:name=\"com.google.android.gms.iid.InstanceID\" /></intent-filter></service>",
+ "count": 1
+ },
+ {
+ "xml": "<service android:exported=\"false\" android:name=\"com.adobe.phonegap.push.RegistrationIntentService\" />",
+ "count": 1
}
],
"/manifest": [
{
"xml": "<uses-permission android:name=\"android.permission.INTERNET\" />",
- "count": 1
+ "count": 2
},
{
"xml": "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />",
- "count": 1
+ "count": 2
},
{
"xml": "<uses-permission android:name=\"android.permission.WAKE_LOCK\" />",
- "count": 1
+ "count": 2
},
{
"xml": "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />",
@@ -94,7 +122,7 @@
},
{
"xml": "<uses-permission android:name=\"android.permission.VIBRATE\" />",
- "count": 1
+ "count": 2
},
{
"xml": "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />",
@@ -102,7 +130,7 @@
},
{
"xml": "<uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />",
- "count": 1
+ "count": 2
},
{
"xml": "<permission android:name=\"us.okfoc.stoneisland.permission.C2D_MESSAGE\" android:protectionLevel=\"signature\" />",
@@ -111,6 +139,22 @@
{
"xml": "<uses-permission android:name=\"us.okfoc.stoneisland.permission.C2D_MESSAGE\" />",
"count": 1
+ },
+ {
+ "xml": "<uses-permission android:name=\"${applicationId}.permission.C2D_MESSAGE\" />",
+ "count": 1
+ },
+ {
+ "xml": "<uses-permission android:name=\"${applicationId}.permission.PushHandlerActivity\" />",
+ "count": 1
+ },
+ {
+ "xml": "<permission android:name=\"${applicationId}.permission.C2D_MESSAGE\" android:protectionLevel=\"signature\" />",
+ "count": 1
+ },
+ {
+ "xml": "<permission android:name=\"${applicationId}.permission.PushHandlerActivity\" android:protectionLevel=\"signature\" />",
+ "count": 1
}
],
"/*/application/activity": [],
@@ -161,6 +205,16 @@
}
]
}
+ },
+ "res/values/strings.xml": {
+ "parents": {
+ "/resources": [
+ {
+ "xml": "<string name=\"google_app_id\">XXXXXXX</string>",
+ "count": 1
+ }
+ ]
+ }
}
}
},
@@ -209,6 +263,10 @@
},
"ionic-plugin-keyboard": {
"PACKAGE_NAME": "us.okfoc.stoneisland"
+ },
+ "phonegap-plugin-push": {
+ "SENDER_ID": "XXXXXXX",
+ "PACKAGE_NAME": "us.okfoc.stoneisland"
}
},
"dependent_plugins": {},
@@ -317,6 +375,14 @@
"cordova.plugins.Keyboard"
],
"runs": true
+ },
+ {
+ "id": "phonegap-plugin-push.PushNotification",
+ "file": "plugins/phonegap-plugin-push/www/push.js",
+ "pluginId": "phonegap-plugin-push",
+ "clobbers": [
+ "PushNotification"
+ ]
}
],
"plugin_metadata": {
@@ -332,6 +398,7 @@
"cordova-plugin-splashscreen": "4.0.0",
"cordova-plugin-compat": "1.1.0",
"cordova-plugin-geolocation": "2.4.0",
- "ionic-plugin-keyboard": "2.2.1"
+ "ionic-plugin-keyboard": "2.2.1",
+ "phonegap-plugin-push": "1.9.2"
}
} \ No newline at end of file
diff --git a/StoneIsland/platforms/android/assets/www/cordova-js-src/android/nativeapiprovider.js b/StoneIsland/platforms/android/assets/www/cordova-js-src/android/nativeapiprovider.js
index 2e9aa67b..2e9aa67b 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova-js-src/android/nativeapiprovider.js
+++ b/StoneIsland/platforms/android/assets/www/cordova-js-src/android/nativeapiprovider.js
diff --git a/StoneIsland/platforms/android/assets/www/cordova-js-src/android/promptbasednativeapi.js b/StoneIsland/platforms/android/assets/www/cordova-js-src/android/promptbasednativeapi.js
index f7fb6bc7..f7fb6bc7 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova-js-src/android/promptbasednativeapi.js
+++ b/StoneIsland/platforms/android/assets/www/cordova-js-src/android/promptbasednativeapi.js
diff --git a/StoneIsland/platforms/android/assets/www/cordova-js-src/exec.js b/StoneIsland/platforms/android/assets/www/cordova-js-src/exec.js
index fa8b41be..f73d87a1 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova-js-src/exec.js
+++ b/StoneIsland/platforms/android/assets/www/cordova-js-src/exec.js
@@ -51,10 +51,11 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
- ONLINE_EVENT: 2
+ ONLINE_EVENT: 2,
+ EVAL_BRIDGE: 3
},
jsToNativeBridgeMode, // Set lazily.
- nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
+ nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,
bridgeSecret = -1;
@@ -77,6 +78,9 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
+ // If args is not provided, default to an empty array
+ args = args || [];
+
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
@@ -86,7 +90,6 @@ function androidExec(success, fail, service, action, args) {
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
-
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
@@ -106,6 +109,17 @@ function androidExec(success, fail, service, action, args) {
}
androidExec.init = function() {
+ //CB-11828
+ //This failsafe checks the version of Android and if it's Jellybean, it switches it to
+ //using the Online Event bridge for communicating from Native to JS
+ //
+ //It's ugly, but it's necessary.
+ var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
+ var version_code = check && check[0].match(/4.[0-3].*/);
+ if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
+ nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
+ }
+
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
diff --git a/StoneIsland/platforms/android/assets/www/cordova-js-src/platform.js b/StoneIsland/platforms/android/assets/www/cordova-js-src/platform.js
index bffc6751..2bfd0247 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova-js-src/platform.js
+++ b/StoneIsland/platforms/android/assets/www/cordova-js-src/platform.js
@@ -19,6 +19,9 @@
*
*/
+// The last resume event that was received that had the result of a plugin call.
+var lastResumeEvent = null;
+
module.exports = {
id: 'android',
bootstrap: function() {
@@ -58,6 +61,19 @@ module.exports = {
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
+ // The resume event is not "sticky", but it is possible that the event
+ // will contain the result of a plugin call. We need to ensure that the
+ // plugin result is delivered even after the event is fired (CB-10498)
+ var cordovaAddEventListener = document.addEventListener;
+
+ document.addEventListener = function(evt, handler, capture) {
+ cordovaAddEventListener(evt, handler, capture);
+
+ if (evt === 'resume' && lastResumeEvent) {
+ handler(lastResumeEvent);
+ }
+ };
+
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
@@ -79,12 +95,30 @@ function onMessageFromNative(msg) {
case 'searchbutton':
// App life cycle events
case 'pause':
- case 'resume':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
+ case 'resume':
+ if(arguments.length > 1 && msg.pendingResult) {
+ if(arguments.length === 2) {
+ msg.pendingResult.result = arguments[1];
+ } else {
+ // The plugin returned a multipart message
+ var res = [];
+ for(var i = 1; i < arguments.length; i++) {
+ res.push(arguments[i]);
+ }
+ msg.pendingResult.result = res;
+ }
+
+ // Save the plugin result so that it can be delivered to the js
+ // even if they miss the initial firing of the event
+ lastResumeEvent = msg;
+ }
+ cordova.fireDocumentEvent(action, msg);
+ break;
default:
throw new Error('Unknown event action ' + action);
}
diff --git a/StoneIsland/platforms/android/assets/www/cordova-js-src/plugin/android/app.js b/StoneIsland/platforms/android/assets/www/cordova-js-src/plugin/android/app.js
index 22cf96e8..22cf96e8 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova-js-src/plugin/android/app.js
+++ b/StoneIsland/platforms/android/assets/www/cordova-js-src/plugin/android/app.js
diff --git a/StoneIsland/platforms/android/assets/www/cordova.js b/StoneIsland/platforms/android/assets/www/cordova.js
index 23f6e475..18c020e7 100755..100644
--- a/StoneIsland/platforms/android/assets/www/cordova.js
+++ b/StoneIsland/platforms/android/assets/www/cordova.js
@@ -1,5 +1,5 @@
// Platform: android
-// 2c29e187e4206a6a77fba940ef6f77aef5c7eb8c
+// 7c5fcc5a5adfbf3fb8ceaf36fbdd4bd970bd9c20
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License.
*/
;(function() {
-var PLATFORM_VERSION_BUILD_LABEL = '4.1.1';
+var PLATFORM_VERSION_BUILD_LABEL = '6.1.2';
// file: src/scripts/require.js
/*jshint -W079 */
@@ -101,7 +101,9 @@ if (typeof module === "object" && typeof require === "function") {
// file: src/cordova.js
define("cordova", function(require, exports, module) {
-if(window.cordova){
+// Workaround for Windows 10 in hosted environment case
+// http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object
+if (window.cordova && !(window.cordova instanceof HTMLElement)) {
throw new Error("cordova already defined");
}
@@ -740,8 +742,13 @@ var Channel = function(type, sticky) {
}
};
-function forceFunction(f) {
- if (typeof f != 'function') throw "Function required as first argument!";
+function checkSubscriptionArgument(argument) {
+ if (typeof argument !== "function" && typeof argument.handleEvent !== "function") {
+ throw new Error(
+ "Must provide a function or an EventListener object " +
+ "implementing the handleEvent interface."
+ );
+ }
}
/**
@@ -751,28 +758,39 @@ function forceFunction(f) {
* and a guid that can be used to stop subscribing to the channel.
* Returns the guid.
*/
-Channel.prototype.subscribe = function(f, c) {
- // need a function to call
- forceFunction(f);
+Channel.prototype.subscribe = function(eventListenerOrFunction, eventListener) {
+ checkSubscriptionArgument(eventListenerOrFunction);
+ var handleEvent, guid;
+
+ if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
+ // Received an EventListener object implementing the handleEvent interface
+ handleEvent = eventListenerOrFunction.handleEvent;
+ eventListener = eventListenerOrFunction;
+ } else {
+ // Received a function to handle event
+ handleEvent = eventListenerOrFunction;
+ }
+
if (this.state == 2) {
- f.apply(c || this, this.fireArgs);
+ handleEvent.apply(eventListener || this, this.fireArgs);
return;
}
- var func = f,
- guid = f.observer_guid;
- if (typeof c == "object") { func = utils.close(c, f); }
+ guid = eventListenerOrFunction.observer_guid;
+ if (typeof eventListener === "object") {
+ handleEvent = utils.close(eventListener, handleEvent);
+ }
if (!guid) {
- // first time any channel has seen this subscriber
+ // First time any channel has seen this subscriber
guid = '' + nextGuid++;
}
- func.observer_guid = guid;
- f.observer_guid = guid;
+ handleEvent.observer_guid = guid;
+ eventListenerOrFunction.observer_guid = guid;
// Don't add the same handler more than once.
if (!this.handlers[guid]) {
- this.handlers[guid] = func;
+ this.handlers[guid] = handleEvent;
this.numHandlers++;
if (this.numHandlers == 1) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
@@ -783,12 +801,20 @@ Channel.prototype.subscribe = function(f, c) {
/**
* Unsubscribes the function with the given guid from the channel.
*/
-Channel.prototype.unsubscribe = function(f) {
- // need a function to unsubscribe
- forceFunction(f);
+Channel.prototype.unsubscribe = function(eventListenerOrFunction) {
+ checkSubscriptionArgument(eventListenerOrFunction);
+ var handleEvent, guid, handler;
- var guid = f.observer_guid,
- handler = this.handlers[guid];
+ if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
+ // Received an EventListener object implementing the handleEvent interface
+ handleEvent = eventListenerOrFunction.handleEvent;
+ } else {
+ // Received a function to handle event
+ handleEvent = eventListenerOrFunction;
+ }
+
+ guid = handleEvent.observer_guid;
+ handler = this.handlers[guid];
if (handler) {
delete this.handlers[guid];
this.numHandlers--;
@@ -895,10 +921,11 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
- ONLINE_EVENT: 2
+ ONLINE_EVENT: 2,
+ EVAL_BRIDGE: 3
},
jsToNativeBridgeMode, // Set lazily.
- nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
+ nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,
bridgeSecret = -1;
@@ -921,6 +948,9 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
+ // If args is not provided, default to an empty array
+ args = args || [];
+
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
@@ -930,7 +960,6 @@ function androidExec(success, fail, service, action, args) {
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
-
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
@@ -950,6 +979,17 @@ function androidExec(success, fail, service, action, args) {
}
androidExec.init = function() {
+ //CB-11828
+ //This failsafe checks the version of Android and if it's Jellybean, it switches it to
+ //using the Online Event bridge for communicating from Native to JS
+ //
+ //It's ugly, but it's necessary.
+ var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
+ var version_code = check && check[0].match(/4.[0-3].*/);
+ if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
+ nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
+ }
+
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
@@ -1291,10 +1331,12 @@ define("cordova/init_b", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
+var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform');
+var pluginloader = require('cordova/pluginloader');
var utils = require('cordova/utils');
-var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
+var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady, channel.onPluginsReady];
// setting exec
cordova.exec = require('cordova/exec');
@@ -1379,10 +1421,19 @@ if (window._nativeReady) {
// Call the platform-specific initialization.
platform.bootstrap && platform.bootstrap();
+// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js.
+// The delay allows the attached modules to be defined before the plugin loader looks for them.
+setTimeout(function() {
+ pluginloader.load(function() {
+ channel.onPluginsReady.fire();
+ });
+}, 0);
+
/**
* Create all cordova objects once native side is ready.
*/
channel.join(function() {
+ modulemapper.mapModules(window);
platform.initialize && platform.initialize();
@@ -1501,9 +1552,109 @@ exports.reset();
});
+// file: src/common/modulemapper_b.js
+define("cordova/modulemapper_b", function(require, exports, module) {
+
+var builder = require('cordova/builder'),
+ symbolList = [],
+ deprecationMap;
+
+exports.reset = function() {
+ symbolList = [];
+ deprecationMap = {};
+};
+
+function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) {
+ symbolList.push(strategy, moduleName, symbolPath);
+ if (opt_deprecationMessage) {
+ deprecationMap[symbolPath] = opt_deprecationMessage;
+ }
+}
+
+// Note: Android 2.3 does have Function.bind().
+exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('c', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('m', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('d', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.runs = function(moduleName) {
+ addEntry('r', moduleName, null);
+};
+
+function prepareNamespace(symbolPath, context) {
+ if (!symbolPath) {
+ return context;
+ }
+ var parts = symbolPath.split('.');
+ var cur = context;
+ for (var i = 0, part; part = parts[i]; ++i) {
+ cur = cur[part] = cur[part] || {};
+ }
+ return cur;
+}
+
+exports.mapModules = function(context) {
+ var origSymbols = {};
+ context.CDV_origSymbols = origSymbols;
+ for (var i = 0, len = symbolList.length; i < len; i += 3) {
+ var strategy = symbolList[i];
+ var moduleName = symbolList[i + 1];
+ var module = require(moduleName);
+ // <runs/>
+ if (strategy == 'r') {
+ continue;
+ }
+ var symbolPath = symbolList[i + 2];
+ var lastDot = symbolPath.lastIndexOf('.');
+ var namespace = symbolPath.substr(0, lastDot);
+ var lastName = symbolPath.substr(lastDot + 1);
+
+ var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null;
+ var parentObj = prepareNamespace(namespace, context);
+ var target = parentObj[lastName];
+
+ if (strategy == 'm' && target) {
+ builder.recursiveMerge(target, module);
+ } else if ((strategy == 'd' && !target) || (strategy != 'd')) {
+ if (!(symbolPath in origSymbols)) {
+ origSymbols[symbolPath] = target;
+ }
+ builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg);
+ }
+ }
+};
+
+exports.getOriginalSymbol = function(context, symbolPath) {
+ var origSymbols = context.CDV_origSymbols;
+ if (origSymbols && (symbolPath in origSymbols)) {
+ return origSymbols[symbolPath];
+ }
+ var parts = symbolPath.split('.');
+ var obj = context;
+ for (var i = 0; i < parts.length; ++i) {
+ obj = obj && obj[parts[i]];
+ }
+ return obj;
+};
+
+exports.reset();
+
+
+});
+
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/platform.js
define("cordova/platform", function(require, exports, module) {
+// The last resume event that was received that had the result of a plugin call.
+var lastResumeEvent = null;
+
module.exports = {
id: 'android',
bootstrap: function() {
@@ -1543,6 +1694,19 @@ module.exports = {
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
+ // The resume event is not "sticky", but it is possible that the event
+ // will contain the result of a plugin call. We need to ensure that the
+ // plugin result is delivered even after the event is fired (CB-10498)
+ var cordovaAddEventListener = document.addEventListener;
+
+ document.addEventListener = function(evt, handler, capture) {
+ cordovaAddEventListener(evt, handler, capture);
+
+ if (evt === 'resume' && lastResumeEvent) {
+ handler(lastResumeEvent);
+ }
+ };
+
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
@@ -1564,12 +1728,30 @@ function onMessageFromNative(msg) {
case 'searchbutton':
// App life cycle events
case 'pause':
- case 'resume':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
+ case 'resume':
+ if(arguments.length > 1 && msg.pendingResult) {
+ if(arguments.length === 2) {
+ msg.pendingResult.result = arguments[1];
+ } else {
+ // The plugin returned a multipart message
+ var res = [];
+ for(var i = 1; i < arguments.length; i++) {
+ res.push(arguments[i]);
+ }
+ msg.pendingResult.result = res;
+ }
+
+ // Save the plugin result so that it can be delivered to the js
+ // even if they miss the initial firing of the event
+ lastResumeEvent = msg;
+ }
+ cordova.fireDocumentEvent(action, msg);
+ break;
default:
throw new Error('Unknown event action ' + action);
}
@@ -1673,10 +1855,6 @@ module.exports = {
// file: src/common/pluginloader.js
define("cordova/pluginloader", function(require, exports, module) {
-/*
- NOTE: this file is NOT used when we use the browserify workflow
-*/
-
var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil');
@@ -1786,6 +1964,54 @@ exports.load = function(callback) {
});
+// file: src/common/pluginloader_b.js
+define("cordova/pluginloader_b", function(require, exports, module) {
+
+var modulemapper = require('cordova/modulemapper');
+
+// Handler for the cordova_plugins.js content.
+// See plugman's plugin_loader.js for the details of this object.
+function handlePluginsObject(moduleList) {
+ // if moduleList is not defined or empty, we've nothing to do
+ if (!moduleList || !moduleList.length) {
+ return;
+ }
+
+ // Loop through all the modules and then through their clobbers and merges.
+ for (var i = 0, module; module = moduleList[i]; i++) {
+ if (module.clobbers && module.clobbers.length) {
+ for (var j = 0; j < module.clobbers.length; j++) {
+ modulemapper.clobbers(module.id, module.clobbers[j]);
+ }
+ }
+
+ if (module.merges && module.merges.length) {
+ for (var k = 0; k < module.merges.length; k++) {
+ modulemapper.merges(module.id, module.merges[k]);
+ }
+ }
+
+ // Finally, if runs is truthy we want to simply require() the module.
+ if (module.runs) {
+ modulemapper.runs(module.id);
+ }
+ }
+}
+
+// Loads all plugins' js-modules. Plugin loading is syncronous in browserified bundle
+// but the method accepts callback to be compatible with non-browserify flow.
+// onDeviceReady is blocked on onPluginsReady. onPluginsReady is fired when there are
+// no plugins to load, or they are all done.
+exports.load = function(callback) {
+ var moduleList = require("cordova/plugin_list");
+ handlePluginsObject(moduleList);
+
+ callback();
+};
+
+
+});
+
// file: src/common/urlutil.js
define("cordova/urlutil", function(require, exports, module) {
@@ -1895,7 +2121,10 @@ utils.clone = function(obj) {
retVal = {};
for(i in obj){
- if(!(i in retVal) || retVal[i] != obj[i]) {
+ // https://issues.apache.org/jira/browse/CB-11522 'unknown' type may be returned in
+ // custom protocol activation case on Windows Phone 8.1 causing "No such interface supported" exception
+ // on cloning.
+ if((!(i in retVal) || retVal[i] != obj[i]) && typeof obj[i] != 'undefined' && typeof obj[i] != 'unknown') {
retVal[i] = utils.clone(obj[i]);
}
}
diff --git a/StoneIsland/platforms/android/assets/www/cordova_plugins.js b/StoneIsland/platforms/android/assets/www/cordova_plugins.js
index a2734881..722b1683 100755
--- a/StoneIsland/platforms/android/assets/www/cordova_plugins.js
+++ b/StoneIsland/platforms/android/assets/www/cordova_plugins.js
@@ -104,6 +104,14 @@ module.exports = [
"cordova.plugins.Keyboard"
],
"runs": true
+ },
+ {
+ "id": "phonegap-plugin-push.PushNotification",
+ "file": "plugins/phonegap-plugin-push/www/push.js",
+ "pluginId": "phonegap-plugin-push",
+ "clobbers": [
+ "PushNotification"
+ ]
}
];
module.exports.metadata =
@@ -121,7 +129,8 @@ module.exports.metadata =
"cordova-plugin-splashscreen": "4.0.0",
"cordova-plugin-compat": "1.1.0",
"cordova-plugin-geolocation": "2.4.0",
- "ionic-plugin-keyboard": "2.2.1"
-}
+ "ionic-plugin-keyboard": "2.2.1",
+ "phonegap-plugin-push": "1.9.2"
+};
// BOTTOM OF METADATA
}); \ No newline at end of file
diff --git a/StoneIsland/platforms/android/assets/www/css/products.css b/StoneIsland/platforms/android/assets/www/css/products.css
index e067e05a..bdfed42a 100755
--- a/StoneIsland/platforms/android/assets/www/css/products.css
+++ b/StoneIsland/platforms/android/assets/www/css/products.css
@@ -24,7 +24,6 @@
height: 126vw;
}
-
.product #product { display: block }
#product {
display: none;
@@ -224,7 +223,11 @@ padding-bottom:45px;
}
#collection h1 {
-background:white
+ background-color: white;
+ background-image: url(../img/angle-down.png);
+ background-size: contain;
+ background-position: top right;
+ background-repeat: no-repeat;
}
#selector {
diff --git a/StoneIsland/platforms/android/assets/www/img/angle-down.png b/StoneIsland/platforms/android/assets/www/img/angle-down.png
new file mode 100644
index 00000000..e0b3b00f
--- /dev/null
+++ b/StoneIsland/platforms/android/assets/www/img/angle-down.png
Binary files differ
diff --git a/StoneIsland/platforms/android/assets/www/index.html b/StoneIsland/platforms/android/assets/www/index.html
index 768e3140..9c86e559 100755
--- a/StoneIsland/platforms/android/assets/www/index.html
+++ b/StoneIsland/platforms/android/assets/www/index.html
@@ -355,7 +355,7 @@
<input type="email" name="Email" placeholder="EMAIL ADDRESS" required>
<input type="email" name="ConfirmEmail" placeholder="CONFIRM EMAIL ADDRESS" required>
<div class="select-wrapper date-wrapper">
- <span>BIRTHDAY (MM/DD/YYYY)</span>
+ <span>BIRTHDAY (OPTIONAL)</span>
<input type="date" name="BirthDay" min="1900-01-01" placeholder="BIRTHDAY (MM/DD/YYYY)" required>
</div>
@@ -1136,6 +1136,7 @@
<script src="js/lib/products/CollectionView.js"></script>
<script src="js/lib/products/filters/CategoryFilter.js"></script>
+<script src="js/lib/products/filters/DepartmentFilter.js"></script>
<script src="js/lib/products/ClosedStoreView.js"></script>
<script src="js/lib/products/ProductView.js"></script>
<script src="js/lib/products/GalleryView.js"></script>
diff --git a/StoneIsland/platforms/android/assets/www/js/index.js b/StoneIsland/platforms/android/assets/www/js/index.js
index 6bea75d0..76a8af05 100755
--- a/StoneIsland/platforms/android/assets/www/js/index.js
+++ b/StoneIsland/platforms/android/assets/www/js/index.js
@@ -8,7 +8,7 @@ var app = (function(){
app.bind()
app.build()
-
+
app.iscroll_options = {
mouseWheel: true,
scrollbars: true,
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/auth/SignupView.js b/StoneIsland/platforms/android/assets/www/js/lib/auth/SignupView.js
index 8d9cf52d..fd0c1887 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/auth/SignupView.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/auth/SignupView.js
@@ -49,7 +49,7 @@ var SignupView = FormView.extend({
"Surname": "Please enter your last name.",
"Email": "Please enter a valid email address.",
"ConfirmEmail": "Please enter a valid email address.",
- "BirthDay": "Please enter your birthday.",
+ // "BirthDay": "Please enter your birthday.",
"Password": "Please enter your password.",
"Password2": "Please enter your password again.",
"DataProfiling": "You must agree to data profiling.",
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/blogs/BlogView.js b/StoneIsland/platforms/android/assets/www/js/lib/blogs/BlogView.js
index b7c80520..5ee7f641 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/blogs/BlogView.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/blogs/BlogView.js
@@ -31,7 +31,7 @@ var BlogView = View.extend({
this.loaded = true
this.data = data = typeof data == "string" ? JSON.parse(data) : data
- switch (data.store[0].StoreStatus) {
+ switch (data.store[0].DepartmentStoreStatus) {
case "open":
app.closed.storeIsClosed = false
break
@@ -46,9 +46,9 @@ var BlogView = View.extend({
app.closed.populate(data.store[0].ClosedStoreImages)
}
else {
- app.gallery_id = data.store[0].CollectionId
- app.department_id = data.store[0].DepartmentId
- app.collection.setCollectionName( data.store[0].collection )
+ app.departments = data.store[0].Departments
+ app.department_id = data.store[0].Departments[0].uri
+ app.collection.setCollectionName( data.store[0].Departments[0].text )
app.collection.loaded = false
app.collection.fetch()
}
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/blogs/HubView.js b/StoneIsland/platforms/android/assets/www/js/lib/blogs/HubView.js
index 1657d295..013c2b45 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/blogs/HubView.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/blogs/HubView.js
@@ -167,6 +167,7 @@ var HubLoader = (function(){
}
HubLoader.build = function(){
view.append(item)
+ view.scroller.refresh()
setTimeout(HubLoader.load, 20)
}
return HubLoader
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/products/CollectionView.js b/StoneIsland/platforms/android/assets/www/js/lib/products/CollectionView.js
index b9a7755f..671d36b3 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/products/CollectionView.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/products/CollectionView.js
@@ -14,6 +14,7 @@ var CollectionView = ScrollableView.extend({
"mousedown .item": "touchstart",
"mousemove .item": "touchmove",
"mouseup .item": "touchend",
+ "touchstart h1": "showDepartmentSelector",
},
initialize: function(){
@@ -21,7 +22,7 @@ var CollectionView = ScrollableView.extend({
this.$content = this.$(".content")
this.$loader = this.$(".loader")
this.scroller = new IScroll('#collection', app.iscroll_options)
- this.filterView = new CategoryFilter ({ parent: this })
+ this.filterView = new DepartmentFilter ({ parent: this })
},
show: function(){
@@ -85,7 +86,7 @@ var CollectionView = ScrollableView.extend({
}
console.log(">>>>>>>> YES ")
if (! this.loaded) {
- console.log("populate 3")
+ console.log("populate 3", data.SearchResponseFull.Results.Items.length)
this.loaded = true
this.$loader.hide()
this.$content.empty()
@@ -140,6 +141,10 @@ var CollectionView = ScrollableView.extend({
this.$title.html(this.collectionName)
},
+ showDepartmentSelector: function(){
+ this.filterView.filter()
+ },
+
firstTouch: { x: 0, y: 0, id: "" },
lastTouch: { x: 0, y: 0, id: "" },
touchstart: function(e){
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/products/ProductView.js b/StoneIsland/platforms/android/assets/www/js/lib/products/ProductView.js
index cfb39f20..81ad536d 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/products/ProductView.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/products/ProductView.js
@@ -118,8 +118,8 @@ var ProductView = ScrollableView.extend({
var title = name_partz.join(' ')
var type = title_case( data['MicroCategory'] )
var price = "$" + data['DiscountedPrice'] + ".00"
- var body = descriptions['Details'] + "<br>" + descriptions['EditorialDescription']
- body = body.replace(/<br>/g, "<br><br>").replace(/(<br>)+$/, "")
+ var body = descriptions['Details'] + " " + descriptions['EditorialDescription']
+ // body = body.replace(/<br>/g, "<br><br>").replace(/(<br>)+$/, "")
var default_color_id = this.populate_selectors(data, details)
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/products/Selector.js b/StoneIsland/platforms/android/assets/www/js/lib/products/Selector.js
index 6db33b68..9c1109f6 100755
--- a/StoneIsland/platforms/android/assets/www/js/lib/products/Selector.js
+++ b/StoneIsland/platforms/android/assets/www/js/lib/products/Selector.js
@@ -5,7 +5,7 @@ var Selector = View.extend({
events: {
"click .close": "hide",
- "click .options div": "pick",
+ "touchstart .options div": "pick",
},
initialize: function(){
@@ -67,4 +67,4 @@ var Selector = View.extend({
this.hide()
},
-}) \ No newline at end of file
+})
diff --git a/StoneIsland/platforms/android/assets/www/js/lib/products/filters/DepartmentFilter.js b/StoneIsland/platforms/android/assets/www/js/lib/products/filters/DepartmentFilter.js
new file mode 100644
index 00000000..cc0d925e
--- /dev/null
+++ b/StoneIsland/platforms/android/assets/www/js/lib/products/filters/DepartmentFilter.js
@@ -0,0 +1,31 @@
+var DepartmentFilter = View.extend({
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ },
+
+ filter: function(){
+ var deps = app.departments.map(function(dep){
+ console.log(dep)
+ return {
+ id: dep.uri,
+ label: dep.text,
+ }
+ })
+ app.selector.select("wide", deps, this.pick.bind(this))
+ },
+
+ last_choice: null,
+
+ pick: function(choice){
+ this.parent.$content.empty()
+ this.last_choice = choice
+ app.department_id = choice.id
+ app.collection.loaded = false
+ app.collection.fetch()
+ app.footer.hide()
+ app.collection.setCollectionName(choice.label)
+ this.parent.deferScrollToTop()
+ },
+
+})
diff --git a/StoneIsland/platforms/android/assets/www/plugins/phonegap-plugin-push/www/push.js b/StoneIsland/platforms/android/assets/www/plugins/phonegap-plugin-push/www/push.js
new file mode 100644
index 00000000..a5315486
--- /dev/null
+++ b/StoneIsland/platforms/android/assets/www/plugins/phonegap-plugin-push/www/push.js
@@ -0,0 +1,329 @@
+cordova.define("phonegap-plugin-push.PushNotification", function(require, exports, module) {
+/* global cordova:false */
+/* globals window */
+
+/*!
+ * Module dependencies.
+ */
+
+var exec = cordova.require('cordova/exec');
+
+/**
+ * PushNotification constructor.
+ *
+ * @param {Object} options to initiate Push Notifications.
+ * @return {PushNotification} instance that can be monitored and cancelled.
+ */
+
+var PushNotification = function(options) {
+ this._handlers = {
+ 'registration': [],
+ 'notification': [],
+ 'error': []
+ };
+
+ // require options parameter
+ if (typeof options === 'undefined') {
+ throw new Error('The options argument is required.');
+ }
+
+ // store the options to this object instance
+ this.options = options;
+
+ // triggered on registration and notification
+ var that = this;
+ var success = function(result) {
+ if (result && typeof result.registrationId !== 'undefined') {
+ that.emit('registration', result);
+ } else if (result && result.additionalData && typeof result.additionalData.actionCallback !== 'undefined') {
+ var executeFuctionOrEmitEventByName = function(callbackName, context, arg) {
+ var namespaces = callbackName.split('.');
+ var func = namespaces.pop();
+ for (var i = 0; i < namespaces.length; i++) {
+ context = context[namespaces[i]];
+ }
+
+ if (typeof context[func] === 'function') {
+ context[func].call(context, arg);
+ } else {
+ that.emit(callbackName, arg);
+ }
+ };
+
+ executeFuctionOrEmitEventByName(result.additionalData.actionCallback, window, result);
+ } else if (result) {
+ that.emit('notification', result);
+ }
+ };
+
+ // triggered on error
+ var fail = function(msg) {
+ var e = (typeof msg === 'string') ? new Error(msg) : msg;
+ that.emit('error', e);
+ };
+
+ // wait at least one process tick to allow event subscriptions
+ setTimeout(function() {
+ exec(success, fail, 'PushNotification', 'init', [options]);
+ }, 10);
+};
+
+/**
+ * Unregister from push notifications
+ */
+
+PushNotification.prototype.unregister = function(successCallback, errorCallback, options) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unregister failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unregister failure: success callback parameter must be a function');
+ return;
+ }
+
+ var that = this;
+ var cleanHandlersAndPassThrough = function() {
+ if (!options) {
+ that._handlers = {
+ 'registration': [],
+ 'notification': [],
+ 'error': []
+ };
+ }
+ successCallback();
+ };
+
+ exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]);
+};
+
+/**
+ * subscribe to a topic
+ * @param {String} topic topic to subscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.subscribe = function(topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]);
+};
+
+/**
+ * unsubscribe to a topic
+ * @param {String} topic topic to unsubscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.unsubscribe = function(topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]);
+};
+
+/**
+ * Call this to set the application icon badge
+ */
+
+PushNotification.prototype.setApplicationIconBadgeNumber = function(successCallback, errorCallback, badge) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [{badge: badge}]);
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.getApplicationIconBadgeNumber = function(successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []);
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.clearAllNotifications = function(successCallback, errorCallback) {
+ if (!successCallback) { successCallback = function() {}; }
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []);
+};
+
+/**
+ * Listen for an event.
+ *
+ * Any event is supported, but the following are built-in:
+ *
+ * - registration
+ * - notification
+ * - error
+ *
+ * @param {String} eventName to subscribe to.
+ * @param {Function} callback triggered on the event.
+ */
+
+PushNotification.prototype.on = function(eventName, callback) {
+ if (!this._handlers.hasOwnProperty(eventName)) {
+ this._handlers[eventName] = [];
+ }
+ this._handlers[eventName].push(callback);
+};
+
+/**
+ * Remove event listener.
+ *
+ * @param {String} eventName to match subscription.
+ * @param {Function} handle function associated with event.
+ */
+
+PushNotification.prototype.off = function (eventName, handle) {
+ if (this._handlers.hasOwnProperty(eventName)) {
+ var handleIndex = this._handlers[eventName].indexOf(handle);
+ if (handleIndex >= 0) {
+ this._handlers[eventName].splice(handleIndex, 1);
+ }
+ }
+};
+
+/**
+ * Emit an event.
+ *
+ * This is intended for internal use only.
+ *
+ * @param {String} eventName is the event to trigger.
+ * @param {*} all arguments are passed to the event listeners.
+ *
+ * @return {Boolean} is true when the event is triggered otherwise false.
+ */
+
+PushNotification.prototype.emit = function() {
+ var args = Array.prototype.slice.call(arguments);
+ var eventName = args.shift();
+
+ if (!this._handlers.hasOwnProperty(eventName)) {
+ return false;
+ }
+
+ for (var i = 0, length = this._handlers[eventName].length; i < length; i++) {
+ var callback = this._handlers[eventName][i];
+ if (typeof callback === 'function') {
+ callback.apply(undefined,args);
+ } else {
+ console.log('event handler: ' + eventName + ' must be a function');
+ }
+ }
+
+ return true;
+};
+
+PushNotification.prototype.finish = function(successCallback, errorCallback, id) {
+ if (!successCallback) { successCallback = function() {}; }
+ if (!errorCallback) { errorCallback = function() {}; }
+ if (!id) { id = 'handler'; }
+
+ if (typeof successCallback !== 'function') {
+ console.log('finish failure: success callback parameter must be a function');
+ return;
+ }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('finish failure: failure parameter not a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]);
+};
+
+/*!
+ * Push Notification Plugin.
+ */
+
+module.exports = {
+ /**
+ * Register for Push Notifications.
+ *
+ * This method will instantiate a new copy of the PushNotification object
+ * and start the registration process.
+ *
+ * @param {Object} options
+ * @return {PushNotification} instance
+ */
+
+ init: function(options) {
+ return new PushNotification(options);
+ },
+
+ hasPermission: function(successCallback, errorCallback) {
+ exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []);
+ },
+
+ /**
+ * PushNotification Object.
+ *
+ * Expose the PushNotification object for direct use
+ * and testing. Typically, you should use the
+ * .init helper method.
+ */
+
+ PushNotification: PushNotification
+};
+
+});
diff --git a/StoneIsland/platforms/android/build.gradle b/StoneIsland/platforms/android/build.gradle
index b9c43320..5eef5255 100755..100644
--- a/StoneIsland/platforms/android/build.gradle
+++ b/StoneIsland/platforms/android/build.gradle
@@ -17,41 +17,35 @@
under the License.
*/
-// GENERATED FILE! DO NOT EDIT!
-
-apply plugin: 'android'
+apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral()
+ jcenter()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
- if (gradle.gradleVersion >= "2.2") {
- dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0+'
- }
- } else if (gradle.gradleVersion >= "2.1") {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.14.0+'
- }
- } else {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.12.0+'
- }
+ dependencies {
+ classpath "com.google.gms:google-services:3.0.0"
+ classpath "com.google.gms:google-services:3.0.0"
+ classpath 'com.android.tools.build:gradle:2.2.1'
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
-repositories {
- mavenCentral()
+allprojects {
+ repositories {
+ mavenCentral();
+ jcenter()
+ }
}
task wrapper(type: Wrapper) {
- gradleVersion = '2.2.1'
+ gradleVersion = '2.14.1'
}
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
@@ -173,25 +167,31 @@ android {
}
defaultConfig {
- versionCode cdvVersionCode ?: Integer.parseInt("" + privateHelpers.extractIntFromManifest("versionCode") + "0")
+ versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
+ applicationId privateHelpers.extractStringFromManifest("package")
+
if (cdvMinSdkVersion != null) {
minSdkVersion cdvMinSdkVersion
}
}
+ lintOptions {
+ abortOnError false;
+ }
+
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
if (Boolean.valueOf(cdvBuildMultipleApks)) {
productFlavors {
armv7 {
- versionCode cdvVersionCode ?: defaultConfig.versionCode + 2
+ versionCode defaultConfig.versionCode*10 + 2
ndk {
abiFilters "armeabi-v7a", ""
}
}
x86 {
- versionCode cdvVersionCode ?: defaultConfig.versionCode + 4
+ versionCode defaultConfig.versionCode*10 + 4
ndk {
abiFilters "x86", ""
}
@@ -202,7 +202,12 @@ android {
}
}
}
- } else if (!cdvVersionCode) {
+ }
+ /*
+
+ ELSE NOTHING! DON'T MESS WITH THE VERSION CODE IF YOU DON'T HAVE TO!
+
+ else if (!cdvVersionCode) {
def minSdkVersion = cdvMinSdkVersion ?: privateHelpers.extractIntFromManifest("minSdkVersion")
// Vary versionCode by the two most common API levels:
// 14 is ICS, which is the lowest API level for many apps.
@@ -213,6 +218,7 @@ android {
defaultConfig.versionCode += 8
}
}
+ */
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
@@ -244,10 +250,11 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
- debugCompile project(path: "CordovaLib", configuration: "debug")
- releaseCompile project(path: "CordovaLib", configuration: "release")
+ debugCompile(project(path: "CordovaLib", configuration: "debug"))
+ releaseCompile(project(path: "CordovaLib", configuration: "release"))
compile "com.android.support:support-v13:23+"
- compile "com.google.android.gms:play-services-gcm:+"
+ compile "com.google.android.gms:play-services-gcm:9.8+"
+ compile "me.leolin:ShortcutBadger:1.1.11@aar"
// SUB-PROJECT DEPENDENCIES END
}
@@ -265,7 +272,7 @@ def promptForReleaseKeyPassword() {
gradle.taskGraph.whenReady { taskGraph ->
taskGraph.getAllTasks().each() { task ->
- if (task.name == 'validateReleaseSigning') {
+ if (task.name == 'validateReleaseSigning' || task.name == 'validateSigningRelease') {
promptForReleaseKeyPassword()
}
}
diff --git a/StoneIsland/platforms/android/cordova/.jshintrc b/StoneIsland/platforms/android/cordova/.jshintrc
new file mode 100644
index 00000000..89a121cf
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/.jshintrc
@@ -0,0 +1,10 @@
+{
+ "node": true
+ , "bitwise": true
+ , "undef": true
+ , "trailing": true
+ , "quotmark": true
+ , "indent": 4
+ , "unused": "vars"
+ , "latedef": "nofunc"
+}
diff --git a/StoneIsland/platforms/android/cordova/Api.js b/StoneIsland/platforms/android/cordova/Api.js
new file mode 100644
index 00000000..8e4711cb
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/Api.js
@@ -0,0 +1,415 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var path = require('path');
+var Q = require('q');
+
+var AndroidProject = require('./lib/AndroidProject');
+var AndroidStudio = require('./lib/AndroidStudio');
+var PluginManager = require('cordova-common').PluginManager;
+
+var CordovaLogger = require('cordova-common').CordovaLogger;
+var selfEvents = require('cordova-common').events;
+
+var PLATFORM = 'android';
+
+
+function setupEvents(externalEventEmitter) {
+ if (externalEventEmitter) {
+ // This will make the platform internal events visible outside
+ selfEvents.forwardEventsTo(externalEventEmitter);
+ return externalEventEmitter;
+ }
+
+ // There is no logger if external emitter is not present,
+ // so attach a console logger
+ CordovaLogger.get().subscribe(selfEvents);
+ return selfEvents;
+}
+
+
+/**
+ * Class, that acts as abstraction over particular platform. Encapsulates the
+ * platform's properties and methods.
+ *
+ * Platform that implements own PlatformApi instance _should implement all
+ * prototype methods_ of this class to be fully compatible with cordova-lib.
+ *
+ * The PlatformApi instance also should define the following field:
+ *
+ * * platform: String that defines a platform name.
+ */
+function Api(platform, platformRootDir, events) {
+ this.platform = PLATFORM;
+ this.root = path.resolve(__dirname, '..');
+
+ setupEvents(events);
+
+ var self = this;
+
+ this.locations = {
+ root: self.root,
+ www: path.join(self.root, 'assets/www'),
+ res: path.join(self.root, 'res'),
+ platformWww: path.join(self.root, 'platform_www'),
+ configXml: path.join(self.root, 'res/xml/config.xml'),
+ defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
+ strings: path.join(self.root, 'res/values/strings.xml'),
+ manifest: path.join(self.root, 'AndroidManifest.xml'),
+ build: path.join(self.root, 'build'),
+ // NOTE: Due to platformApi spec we need to return relative paths here
+ cordovaJs: 'bin/templates/project/assets/www/cordova.js',
+ cordovaJsSrc: 'cordova-js-src'
+ };
+
+ // XXX Override some locations for Android Studio projects
+ if(AndroidStudio.isAndroidStudioProject(self.root) === true) {
+ selfEvents.emit('log', 'Android Studio project detected');
+ this.android_studio = true;
+ this.locations.configXml = path.join(self.root, 'app/src/main/res/xml/config.xml');
+ this.locations.strings = path.join(self.root, 'app/src/main/res/xml/strings.xml');
+ this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml');
+ this.locations.www = path.join(self.root, 'app/src/main/assets/www');
+ this.locations.res = path.join(self.root, 'app/src/main/res');
+ }
+}
+
+/**
+ * Installs platform to specified directory and creates a platform project.
+ *
+ * @param {String} destination Destination directory, where insatll platform to
+ * @param {ConfigParser} [config] ConfgiParser instance, used to retrieve
+ * project creation options, such as package id and project name.
+ * @param {Object} [options] An options object. The most common options are:
+ * @param {String} [options.customTemplate] A path to custom template, that
+ * should override the default one from platform.
+ * @param {Boolean} [options.link] Flag that indicates that platform's
+ * sources will be linked to installed platform instead of copying.
+ * @param {EventEmitter} [events] An EventEmitter instance that will be used for
+ * logging purposes. If no EventEmitter provided, all events will be logged to
+ * console
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ * instance or rejected with CordovaError.
+ */
+Api.createPlatform = function (destination, config, options, events) {
+ events = setupEvents(events);
+ var result;
+ try {
+ result = require('../../lib/create')
+ .create(destination, config, options, events)
+ .then(function (destination) {
+ var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
+ return new PlatformApi(PLATFORM, destination, events);
+ });
+ }
+ catch (e) {
+ events.emit('error','createPlatform is not callable from the android project API.');
+ throw(e);
+ }
+ return result;
+};
+
+/**
+ * Updates already installed platform.
+ *
+ * @param {String} destination Destination directory, where platform installed
+ * @param {Object} [options] An options object. The most common options are:
+ * @param {String} [options.customTemplate] A path to custom template, that
+ * should override the default one from platform.
+ * @param {Boolean} [options.link] Flag that indicates that platform's
+ * sources will be linked to installed platform instead of copying.
+ * @param {EventEmitter} [events] An EventEmitter instance that will be used for
+ * logging purposes. If no EventEmitter provided, all events will be logged to
+ * console
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ * instance or rejected with CordovaError.
+ */
+Api.updatePlatform = function (destination, options, events) {
+ events = setupEvents(events);
+ var result;
+ try {
+ result = require('../../lib/create')
+ .update(destination, options, events)
+ .then(function (destination) {
+ var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
+ return new PlatformApi('android', destination, events);
+ });
+ }
+ catch (e) {
+ events.emit('error','updatePlatform is not callable from the android project API, you will need to do this manually.');
+ throw(e);
+ }
+ return result;
+};
+
+/**
+ * Gets a CordovaPlatform object, that represents the platform structure.
+ *
+ * @return {CordovaPlatform} A structure that contains the description of
+ * platform's file structure and other properties of platform.
+ */
+Api.prototype.getPlatformInfo = function () {
+ var result = {};
+ result.locations = this.locations;
+ result.root = this.root;
+ result.name = this.platform;
+ result.version = require('./version');
+ result.projectConfig = this._config;
+
+ return result;
+};
+
+/**
+ * Updates installed platform with provided www assets and new app
+ * configuration. This method is required for CLI workflow and will be called
+ * each time before build, so the changes, made to app configuration and www
+ * code, will be applied to platform.
+ *
+ * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
+ * project structure and configuration, that should be applied to platform
+ * (contains project's www location and ConfigParser instance for project's
+ * config).
+ *
+ * @return {Promise} Return a promise either fulfilled, or rejected with
+ * CordovaError instance.
+ */
+Api.prototype.prepare = function (cordovaProject, prepareOptions) {
+ return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions);
+};
+
+/**
+ * Installs a new plugin into platform. This method only copies non-www files
+ * (sources, libs, etc.) to platform. It also doesn't resolves the
+ * dependencies of plugin. Both of handling of www files, such as assets and
+ * js-files and resolving dependencies are the responsibility of caller.
+ *
+ * @param {PluginInfo} plugin A PluginInfo instance that represents plugin
+ * that will be installed.
+ * @param {Object} installOptions An options object. Possible options below:
+ * @param {Boolean} installOptions.link: Flag that specifies that plugin
+ * sources will be symlinked to app's directory instead of copying (if
+ * possible).
+ * @param {Object} installOptions.variables An object that represents
+ * variables that will be used to install plugin. See more details on plugin
+ * variables in documentation:
+ * https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
+ *
+ * @return {Promise} Return a promise either fulfilled, or rejected with
+ * CordovaError instance.
+ */
+Api.prototype.addPlugin = function (plugin, installOptions) {
+ var project = AndroidProject.getProjectFile(this.root);
+ var self = this;
+
+ installOptions = installOptions || {};
+ installOptions.variables = installOptions.variables || {};
+ // Add PACKAGE_NAME variable into vars
+ if (!installOptions.variables.PACKAGE_NAME) {
+ installOptions.variables.PACKAGE_NAME = project.getPackageName();
+ }
+
+ if(this.android_studio === true) {
+ installOptions.android_studio = true;
+ }
+
+ return Q()
+ .then(function () {
+ //CB-11964: Do a clean when installing the plugin code to get around
+ //the Gradle bug introduced by the Android Gradle Plugin Version 2.2
+ //TODO: Delete when the next version of Android Gradle plugin comes out
+
+ // Since clean doesn't just clean the build, it also wipes out www, we need
+ // to pass additional options.
+
+ // Do some basic argument parsing
+ var opts = {};
+
+ // Skip cleaning prepared files when not invoking via cordova CLI.
+ opts.noPrepare = true;
+
+ if(!AndroidStudio.isAndroidStudioProject(self.root) && !project.isClean()) {
+ return self.clean(opts);
+ }
+ })
+ .then(function () {
+ return PluginManager.get(self.platform, self.locations, project)
+ .addPlugin(plugin, installOptions);
+ })
+ .then(function () {
+ if (plugin.getFrameworks(this.platform).length === 0) return;
+
+ selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
+ require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+ }.bind(this))
+ // CB-11022 Return truthy value to prevent running prepare after
+ .thenResolve(true);
+};
+
+/**
+ * Removes an installed plugin from platform.
+ *
+ * Since method accepts PluginInfo instance as input parameter instead of plugin
+ * id, caller shoud take care of managing/storing PluginInfo instances for
+ * future uninstalls.
+ *
+ * @param {PluginInfo} plugin A PluginInfo instance that represents plugin
+ * that will be installed.
+ *
+ * @return {Promise} Return a promise either fulfilled, or rejected with
+ * CordovaError instance.
+ */
+Api.prototype.removePlugin = function (plugin, uninstallOptions) {
+ var project = AndroidProject.getProjectFile(this.root);
+
+ if(uninstallOptions && uninstallOptions.usePlatformWww === true && this.android_studio === true) {
+ uninstallOptions.usePlatformWww = false;
+ uninstallOptions.android_studio = true;
+ }
+
+ return PluginManager.get(this.platform, this.locations, project)
+ .removePlugin(plugin, uninstallOptions)
+ .then(function () {
+ if (plugin.getFrameworks(this.platform).length === 0) return;
+
+ selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
+ require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+ }.bind(this))
+ // CB-11022 Return truthy value to prevent running prepare after
+ .thenResolve(true);
+};
+
+/**
+ * Builds an application package for current platform.
+ *
+ * @param {Object} buildOptions A build options. This object's structure is
+ * highly depends on platform's specific. The most common options are:
+ * @param {Boolean} buildOptions.debug Indicates that packages should be
+ * built with debug configuration. This is set to true by default unless the
+ * 'release' option is not specified.
+ * @param {Boolean} buildOptions.release Indicates that packages should be
+ * built with release configuration. If not set to true, debug configuration
+ * will be used.
+ * @param {Boolean} buildOptions.device Specifies that built app is intended
+ * to run on device
+ * @param {Boolean} buildOptions.emulator: Specifies that built app is
+ * intended to run on emulator
+ * @param {String} buildOptions.target Specifies the device id that will be
+ * used to run built application.
+ * @param {Boolean} buildOptions.nobuild Indicates that this should be a
+ * dry-run call, so no build artifacts will be produced.
+ * @param {String[]} buildOptions.archs Specifies chip architectures which
+ * app packages should be built for. List of valid architectures is depends on
+ * platform.
+ * @param {String} buildOptions.buildConfig The path to build configuration
+ * file. The format of this file is depends on platform.
+ * @param {String[]} buildOptions.argv Raw array of command-line arguments,
+ * passed to `build` command. The purpose of this property is to pass a
+ * platform-specific arguments, and eventually let platform define own
+ * arguments processing logic.
+ *
+ * @return {Promise<Object[]>} A promise either fulfilled with an array of build
+ * artifacts (application packages) if package was built successfully,
+ * or rejected with CordovaError. The resultant build artifact objects is not
+ * strictly typed and may conatin arbitrary set of fields as in sample below.
+ *
+ * {
+ * architecture: 'x86',
+ * buildType: 'debug',
+ * path: '/path/to/build',
+ * type: 'app'
+ * }
+ *
+ * The return value in most cases will contain only one item but in some cases
+ * there could be multiple items in output array, e.g. when multiple
+ * arhcitectures is specified.
+ */
+Api.prototype.build = function (buildOptions) {
+ var self = this;
+ return require('./lib/check_reqs').run()
+ .then(function () {
+ return require('./lib/build').run.call(self, buildOptions);
+ })
+ .then(function (buildResults) {
+ // Cast build result to array of build artifacts
+ return buildResults.apkPaths.map(function (apkPath) {
+ return {
+ buildType: buildResults.buildType,
+ buildMethod: buildResults.buildMethod,
+ path: apkPath,
+ type: 'apk'
+ };
+ });
+ });
+};
+
+/**
+ * Builds an application package for current platform and runs it on
+ * specified/default device. If no 'device'/'emulator'/'target' options are
+ * specified, then tries to run app on default device if connected, otherwise
+ * runs the app on emulator.
+ *
+ * @param {Object} runOptions An options object. The structure is the same
+ * as for build options.
+ *
+ * @return {Promise} A promise either fulfilled if package was built and ran
+ * successfully, or rejected with CordovaError.
+ */
+Api.prototype.run = function(runOptions) {
+ var self = this;
+ return require('./lib/check_reqs').run()
+ .then(function () {
+ return require('./lib/run').run.call(self, runOptions);
+ });
+};
+
+/**
+ * Cleans out the build artifacts from platform's directory, and also
+ * cleans out the platform www directory if called without options specified.
+ *
+ * @return {Promise} Return a promise either fulfilled, or rejected with
+ * CordovaError.
+ */
+Api.prototype.clean = function(cleanOptions) {
+ var self = this;
+ return require('./lib/check_reqs').run()
+ .then(function () {
+ return require('./lib/build').runClean.call(self, cleanOptions);
+ })
+ .then(function () {
+ return require('./lib/prepare').clean.call(self, cleanOptions);
+ });
+};
+
+
+
+/**
+ * Performs a requirements check for current platform. Each platform defines its
+ * own set of requirements, which should be resolved before platform can be
+ * built successfully.
+ *
+ * @return {Promise<Requirement[]>} Promise, resolved with set of Requirement
+ * objects for current platform.
+ */
+Api.prototype.requirements = function() {
+ return require('./lib/check_reqs').check_all();
+};
+
+module.exports = Api;
diff --git a/StoneIsland/platforms/android/cordova/build b/StoneIsland/platforms/android/cordova/build
index 3c3aee4e..222e84a0 100755
--- a/StoneIsland/platforms/android/cordova/build
+++ b/StoneIsland/platforms/android/cordova/build
@@ -19,23 +19,32 @@
under the License.
*/
-var build = require('./lib/build'),
- reqs = require('./lib/check_reqs'),
- args = process.argv;
+var args = process.argv;
+var Api = require('./Api');
+var nopt = require('nopt');
+var path = require('path');
// Support basic help commands
-if(args[2] == '--help' ||
- args[2] == '/?' ||
- args[2] == '-h' ||
- args[2] == 'help' ||
- args[2] == '-help' ||
- args[2] == '/help') {
- build.help();
-} else {
- reqs.run().done(function() {
- return build.run(args.slice(2));
- }, function(err) {
- console.error(err);
- process.exit(2);
- });
-}
+if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
+ require('./lib/build').help();
+
+// Do some basic argument parsing
+var buildOpts = nopt({
+ 'verbose' : Boolean,
+ 'silent' : Boolean,
+ 'debug' : Boolean,
+ 'release' : Boolean,
+ 'nobuild': Boolean,
+ 'buildConfig' : path
+}, { 'd' : '--verbose' });
+
+// Make buildOptions compatible with PlatformApi build method spec
+buildOpts.argv = buildOpts.argv.original;
+
+require('./loggingHelper').adjustLoggerLevel(buildOpts);
+
+new Api().build(buildOpts)
+.catch(function(err) {
+ console.error(err.stack);
+ process.exit(2);
+});
diff --git a/StoneIsland/platforms/android/cordova/build.bat b/StoneIsland/platforms/android/cordova/build.bat
index 46e966af..46e966af 100755..100644
--- a/StoneIsland/platforms/android/cordova/build.bat
+++ b/StoneIsland/platforms/android/cordova/build.bat
diff --git a/StoneIsland/platforms/android/cordova/check_reqs.bat b/StoneIsland/platforms/android/cordova/check_reqs.bat
index cb2c6f54..cb2c6f54 100755..100644
--- a/StoneIsland/platforms/android/cordova/check_reqs.bat
+++ b/StoneIsland/platforms/android/cordova/check_reqs.bat
diff --git a/StoneIsland/platforms/android/cordova/clean b/StoneIsland/platforms/android/cordova/clean
index d9a7d490..22065cc5 100755
--- a/StoneIsland/platforms/android/cordova/clean
+++ b/StoneIsland/platforms/android/cordova/clean
@@ -19,26 +19,33 @@
under the License.
*/
-var build = require('./lib/build'),
- reqs = require('./lib/check_reqs'),
- args = process.argv;
+var Api = require('./Api');
var path = require('path');
+var nopt = require('nopt');
// Support basic help commands
-if(args[2] == '--help' ||
- args[2] == '/?' ||
- args[2] == '-h' ||
- args[2] == 'help' ||
- args[2] == '-help' ||
- args[2] == '/help') {
+if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
console.log('Cleans the project directory.');
process.exit(0);
-} else {
- reqs.run().done(function() {
- return build.runClean(args.slice(2));
- }, function(err) {
- console.error(err);
- process.exit(2);
- });
}
+
+// Do some basic argument parsing
+var opts = nopt({
+ 'verbose' : Boolean,
+ 'silent' : Boolean
+}, { 'd' : '--verbose' });
+
+// Make buildOptions compatible with PlatformApi clean method spec
+opts.argv = opts.argv.original;
+
+// Skip cleaning prepared files when not invoking via cordova CLI.
+opts.noPrepare = true;
+
+require('./loggingHelper').adjustLoggerLevel(opts);
+
+new Api().clean(opts)
+.catch(function(err) {
+ console.error(err.stack);
+ process.exit(2);
+});
diff --git a/StoneIsland/platforms/android/cordova/clean.bat b/StoneIsland/platforms/android/cordova/clean.bat
index 445ef6e1..445ef6e1 100755..100644
--- a/StoneIsland/platforms/android/cordova/clean.bat
+++ b/StoneIsland/platforms/android/cordova/clean.bat
diff --git a/StoneIsland/platforms/android/cordova/defaults.xml b/StoneIsland/platforms/android/cordova/defaults.xml
index 5286ab9c..5286ab9c 100755..100644
--- a/StoneIsland/platforms/android/cordova/defaults.xml
+++ b/StoneIsland/platforms/android/cordova/defaults.xml
diff --git a/StoneIsland/platforms/android/cordova/lib/Adb.js b/StoneIsland/platforms/android/cordova/lib/Adb.js
new file mode 100644
index 00000000..84ae707e
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/Adb.js
@@ -0,0 +1,105 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var os = require('os');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+
+var Adb = {};
+
+function isDevice(line) {
+ return line.match(/\w+\tdevice/) && !line.match(/emulator/);
+}
+
+function isEmulator(line) {
+ return line.match(/device/) && line.match(/emulator/);
+}
+
+/**
+ * Lists available/connected devices and emulators
+ *
+ * @param {Object} opts Various options
+ * @param {Boolean} opts.emulators Specifies whether this method returns
+ * emulators only
+ *
+ * @return {Promise<String[]>} list of available/connected
+ * devices/emulators
+ */
+Adb.devices = function (opts) {
+ return spawn('adb', ['devices'], {cwd: os.tmpdir()})
+ .then(function(output) {
+ return output.split('\n').filter(function (line) {
+ // Filter out either real devices or emulators, depending on options
+ return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
+ }).map(function (line) {
+ return line.replace(/\tdevice/, '').replace('\r', '');
+ });
+ });
+};
+
+Adb.install = function (target, packagePath, opts) {
+ events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
+ var args = ['-s', target, 'install'];
+ if (opts && opts.replace) args.push('-r');
+ return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()})
+ .then(function(output) {
+ // 'adb install' seems to always returns no error, even if installation fails
+ // so we catching output to detect installation failure
+ if (output.match(/Failure/)) {
+ if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
+ output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
+ ' or sign and deploy the unsigned apk manually using Android tools.';
+ } else if (output.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
+ output += '\n\n' + 'You\'re trying to install apk with a lower versionCode that is already installed.' +
+ '\nEither uninstall an app or increment the versionCode.';
+ }
+
+ return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
+ }
+ });
+};
+
+Adb.uninstall = function (target, packageId) {
+ events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...');
+ return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
+};
+
+Adb.shell = function (target, shellCommand) {
+ events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
+ var args = ['-s', target, 'shell'];
+ shellCommand = shellCommand.split(/\s+/);
+ return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()})
+ .catch(function (output) {
+ return Q.reject(new CordovaError('Failed to execute shell command "' +
+ shellCommand + '"" on device: ' + output));
+ });
+};
+
+Adb.start = function (target, activityName) {
+ events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
+ return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName)
+ .catch(function (output) {
+ return Q.reject(new CordovaError('Failed to start application "' +
+ activityName + '"" on device: ' + output));
+ });
+};
+
+module.exports = Adb;
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js b/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js
new file mode 100644
index 00000000..8248f593
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidManifest.js
@@ -0,0 +1,161 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs');
+var et = require('elementtree');
+var xml= require('cordova-common').xmlHelpers;
+
+var DEFAULT_ORIENTATION = 'default';
+
+/** Wraps an AndroidManifest file */
+function AndroidManifest(path) {
+ this.path = path;
+ this.doc = xml.parseElementtreeSync(path);
+ if (this.doc.getroot().tag !== 'manifest') {
+ throw new Error('AndroidManifest at ' + path + ' has incorrect root node name (expected "manifest")');
+ }
+}
+
+AndroidManifest.prototype.getVersionName = function() {
+ return this.doc.getroot().attrib['android:versionName'];
+};
+
+AndroidManifest.prototype.setVersionName = function(versionName) {
+ this.doc.getroot().attrib['android:versionName'] = versionName;
+ return this;
+};
+
+AndroidManifest.prototype.getVersionCode = function() {
+ return this.doc.getroot().attrib['android:versionCode'];
+};
+
+AndroidManifest.prototype.setVersionCode = function(versionCode) {
+ this.doc.getroot().attrib['android:versionCode'] = versionCode;
+ return this;
+};
+
+AndroidManifest.prototype.getPackageId = function() {
+ /*jshint -W069 */
+ return this.doc.getroot().attrib['package'];
+ /*jshint +W069 */
+};
+
+AndroidManifest.prototype.setPackageId = function(pkgId) {
+ /*jshint -W069 */
+ this.doc.getroot().attrib['package'] = pkgId;
+ /*jshint +W069 */
+ return this;
+};
+
+AndroidManifest.prototype.getActivity = function() {
+ var activity = this.doc.getroot().find('./application/activity');
+ return {
+ getName: function () {
+ return activity.attrib['android:name'];
+ },
+ setName: function (name) {
+ if (!name) {
+ delete activity.attrib['android:name'];
+ } else {
+ activity.attrib['android:name'] = name;
+ }
+ return this;
+ },
+ getOrientation: function () {
+ return activity.attrib['android:screenOrientation'];
+ },
+ setOrientation: function (orientation) {
+ if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
+ delete activity.attrib['android:screenOrientation'];
+ } else {
+ activity.attrib['android:screenOrientation'] = orientation;
+ }
+ return this;
+ },
+ getLaunchMode: function () {
+ return activity.attrib['android:launchMode'];
+ },
+ setLaunchMode: function (launchMode) {
+ if (!launchMode) {
+ delete activity.attrib['android:launchMode'];
+ } else {
+ activity.attrib['android:launchMode'] = launchMode;
+ }
+ return this;
+ }
+ };
+};
+
+['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion']
+.forEach(function(sdkPrefName) {
+ // Copy variable reference to avoid closure issues
+ var prefName = sdkPrefName;
+
+ AndroidManifest.prototype['get' + capitalize(prefName)] = function() {
+ var usesSdk = this.doc.getroot().find('./uses-sdk');
+ return usesSdk && usesSdk.attrib['android:' + prefName];
+ };
+
+ AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) {
+ var usesSdk = this.doc.getroot().find('./uses-sdk');
+
+ if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
+ usesSdk = new et.Element('uses-sdk');
+ this.doc.getroot().append(usesSdk);
+ }
+
+ if (prefValue) {
+ usesSdk.attrib['android:' + prefName] = prefValue;
+ }
+
+ return this;
+ };
+});
+
+AndroidManifest.prototype.getDebuggable = function() {
+ return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
+};
+
+AndroidManifest.prototype.setDebuggable = function(value) {
+ var application = this.doc.getroot().find('./application');
+ if (value) {
+ application.attrib['android:debuggable'] = 'true';
+ } else {
+ // The default value is "false", so we can remove attribute at all.
+ delete application.attrib['android:debuggable'];
+ }
+ return this;
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ * will be written to that file
+ *
+ * @param {String} [destPath] File to write manifest to. If omitted,
+ * manifest will be written to file it has been read from.
+ */
+AndroidManifest.prototype.write = function(destPath) {
+ fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
+};
+
+module.exports = AndroidManifest;
+
+function capitalize (str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidProject.js b/StoneIsland/platforms/android/cordova/lib/AndroidProject.js
new file mode 100644
index 00000000..fa1c6129
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidProject.js
@@ -0,0 +1,210 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs');
+var path = require('path');
+var properties_parser = require('properties-parser');
+var AndroidManifest = require('./AndroidManifest');
+var AndroidStudio = require('./AndroidStudio');
+var pluginHandlers = require('./pluginHandlers');
+
+var projectFileCache = {};
+
+function addToPropertyList(projectProperties, key, value) {
+ var i = 1;
+ while (projectProperties.get(key + '.' + i))
+ i++;
+
+ projectProperties.set(key + '.' + i, value);
+ projectProperties.dirty = true;
+}
+
+function removeFromPropertyList(projectProperties, key, value) {
+ var i = 1;
+ var currentValue;
+ while ((currentValue = projectProperties.get(key + '.' + i))) {
+ if (currentValue === value) {
+ while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
+ projectProperties.set(key + '.' + i, currentValue);
+ i++;
+ }
+ projectProperties.set(key + '.' + i);
+ break;
+ }
+ i++;
+ }
+ projectProperties.dirty = true;
+}
+
+function getRelativeLibraryPath (parentDir, subDir) {
+ var libraryPath = path.relative(parentDir, subDir);
+ return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
+}
+
+function AndroidProject(projectDir) {
+ this._propertiesEditors = {};
+ this._subProjectDirs = {};
+ this._dirty = false;
+ this.projectDir = projectDir;
+ this.platformWww = path.join(this.projectDir, 'platform_www');
+ this.www = path.join(this.projectDir, 'assets/www');
+ if(AndroidStudio.isAndroidStudioProject(projectDir) === true) {
+ this.www = path.join(this.projectDir, 'app/src/main/assets/www');
+ }
+}
+
+AndroidProject.getProjectFile = function (projectDir) {
+ if (!projectFileCache[projectDir]) {
+ projectFileCache[projectDir] = new AndroidProject(projectDir);
+ }
+
+ return projectFileCache[projectDir];
+};
+
+AndroidProject.purgeCache = function (projectDir) {
+ if (projectDir) {
+ delete projectFileCache[projectDir];
+ } else {
+ projectFileCache = {};
+ }
+};
+
+/**
+ * Reads the package name out of the Android Manifest file
+ *
+ * @param {String} projectDir The absolute path to the directory containing the project
+ *
+ * @return {String} The name of the package
+ */
+AndroidProject.prototype.getPackageName = function() {
+ var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml');
+ if(AndroidStudio.isAndroidStudioProject(this.projectDir) === true) {
+ manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
+ }
+ return new AndroidManifest(manifestPath).getPackageId();
+};
+
+AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) {
+ // All custom subprojects are prefixed with the last portion of the package id.
+ // This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
+ var packageName = this.getPackageName();
+ var lastDotIndex = packageName.lastIndexOf('.');
+ var prefix = packageName.substring(lastDotIndex + 1);
+ var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
+ return subRelativeDir;
+};
+
+AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var subProjectFile = path.resolve(subDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ // TODO: Setting the target needs to happen only for pre-3.7.0 projects
+ if (fs.existsSync(subProjectFile)) {
+ var subProperties = this._getPropertiesFile(subProjectFile);
+ subProperties.set('target', parentProperties.get('target'));
+ subProperties.dirty = true;
+ this._subProjectDirs[subDir] = true;
+ }
+ addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+ delete this._subProjectDirs[subDir];
+ this._dirty = true;
+};
+
+AndroidProject.prototype.addGradleReference = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+ this._dirty = true;
+};
+
+AndroidProject.prototype.addSystemLibrary = function(parentDir, value) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ addToPropertyList(parentProperties, 'cordova.system.library', value);
+ this._dirty = true;
+};
+
+AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) {
+ var parentProjectFile = path.resolve(parentDir, 'project.properties');
+ var parentProperties = this._getPropertiesFile(parentProjectFile);
+ removeFromPropertyList(parentProperties, 'cordova.system.library', value);
+ this._dirty = true;
+};
+
+AndroidProject.prototype.write = function() {
+ if (!this._dirty) {
+ return;
+ }
+ this._dirty = false;
+
+ for (var filename in this._propertiesEditors) {
+ var editor = this._propertiesEditors[filename];
+ if (editor.dirty) {
+ fs.writeFileSync(filename, editor.toString());
+ editor.dirty = false;
+ }
+ }
+};
+
+AndroidProject.prototype._getPropertiesFile = function (filename) {
+ if (!this._propertiesEditors[filename]) {
+ if (fs.existsSync(filename)) {
+ this._propertiesEditors[filename] = properties_parser.createEditor(filename);
+ } else {
+ this._propertiesEditors[filename] = properties_parser.createEditor();
+ }
+ }
+
+ return this._propertiesEditors[filename];
+};
+
+AndroidProject.prototype.getInstaller = function (type) {
+ return pluginHandlers.getInstaller(type);
+};
+
+AndroidProject.prototype.getUninstaller = function (type) {
+ return pluginHandlers.getUninstaller(type);
+};
+
+/*
+ * This checks if an Android project is clean or has old build artifacts
+ */
+
+AndroidProject.prototype.isClean = function() {
+ var build_path = path.join(this.projectDir, 'build');
+ //If the build directory doesn't exist, it's clean
+ return !(fs.existsSync(build_path));
+};
+
+module.exports = AndroidProject;
diff --git a/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js b/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js
new file mode 100644
index 00000000..335b334b
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/AndroidStudio.js
@@ -0,0 +1,42 @@
+/*
+ * This is a simple routine that checks if project is an Android Studio Project
+ *
+ * @param {String} root Root folder of the project
+ */
+
+/*jshint esnext: false */
+
+var path = require('path');
+var fs = require('fs');
+var CordovaError = require('cordova-common').CordovaError;
+
+module.exports.isAndroidStudioProject = function isAndroidStudioProject(root) {
+ var eclipseFiles = ['AndroidManifest.xml', 'libs', 'res', 'project.properties', 'platform_www'];
+ var androidStudioFiles = ['app', 'gradle', 'app/src/main/res'];
+
+ // assume it is an AS project and not an Eclipse project
+ var isEclipse = false;
+ var isAS = true;
+
+ if(!fs.existsSync(root)) {
+ throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root);
+ }
+
+ // if any of the following exists, then we are not an ASProj
+ eclipseFiles.forEach(function(file) {
+ if(fs.existsSync(path.join(root, file))) {
+ isEclipse = true;
+ }
+ });
+
+ // if it is NOT an eclipse project, check that all required files exist
+ if(!isEclipse) {
+ androidStudioFiles.forEach(function(file){
+ if(!fs.existsSync(path.join(root, file))) {
+ console.log('missing file :: ' + file);
+ isAS = false;
+ }
+ });
+ }
+ return (!isEclipse && isAS);
+};
diff --git a/StoneIsland/platforms/android/cordova/lib/appinfo.js b/StoneIsland/platforms/android/cordova/lib/appinfo.js
deleted file mode 100755
index 080c2ba8..00000000
--- a/StoneIsland/platforms/android/cordova/lib/appinfo.js
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var path = require('path');
-var fs = require('fs');
-var cachedAppInfo = null;
-
-function readAppInfoFromManifest() {
- var manifestPath = path.join(__dirname, '..', '..', 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, {encoding:'utf8'});
- var packageName = /\bpackage\s*=\s*"(.+?)"/.exec(manifestData);
- if (!packageName) throw new Error('Could not find package name within ' + manifestPath);
- var activityTag = /<activity\b[\s\S]*<\/activity>/.exec(manifestData);
- if (!activityTag) throw new Error('Could not find <activity> within ' + manifestPath);
- var activityName = /\bandroid:name\s*=\s*"(.+?)"/.exec(activityTag);
- if (!activityName) throw new Error('Could not find android:name within ' + manifestPath);
-
- return packageName[1] + '/.' + activityName[1];
-}
-
-exports.getActivityName = function() {
- return (cachedAppInfo = cachedAppInfo || readAppInfoFromManifest());
-};
diff --git a/StoneIsland/platforms/android/cordova/lib/build.js b/StoneIsland/platforms/android/cordova/lib/build.js
index aa9f3d01..bd613da2 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/build.js
+++ b/StoneIsland/platforms/android/cordova/lib/build.js
@@ -19,477 +19,68 @@
under the License.
*/
-/* jshint sub:true */
-
-var shell = require('shelljs'),
- spawn = require('./spawn'),
- Q = require('q'),
+var Q = require('q'),
path = require('path'),
fs = require('fs'),
- os = require('os'),
- ROOT = path.join(__dirname, '..', '..');
-var check_reqs = require('./check_reqs');
-var exec = require('./exec');
-
+ nopt = require('nopt');
-var SIGNING_PROPERTIES = '-signing.properties';
-var MARKER = 'YOUR CHANGES WILL BE ERASED!';
-var TEMPLATE =
- '# This file is automatically generated.\n' +
- '# Do not modify this file -- ' + MARKER + '\n';
-
-function findApks(directory) {
- var ret = [];
- if (fs.existsSync(directory)) {
- fs.readdirSync(directory).forEach(function(p) {
- if (path.extname(p) == '.apk') {
- ret.push(path.join(directory, p));
- }
- });
- }
- return ret;
-}
+var Adb = require('./Adb');
-function sortFilesByDate(files) {
- return files.map(function(p) {
- return { p: p, t: fs.statSync(p).mtime };
- }).sort(function(a, b) {
- var timeDiff = b.t - a.t;
- return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
- }).map(function(p) { return p.p; });
-}
+var builders = require('./builders/builders');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
-function isAutoGenerated(file) {
- if(fs.existsSync(file)) {
- var fileContents = fs.readFileSync(file, 'utf8');
- return fileContents.indexOf(MARKER) > 0;
- }
- return false;
-}
+function parseOpts(options, resolvedTarget, projectRoot) {
+ options = options || {};
+ options.argv = nopt({
+ gradle: Boolean,
+ ant: Boolean,
+ prepenv: Boolean,
+ versionCode: String,
+ minSdkVersion: String,
+ gradleArg: [String, Array],
+ keystore: path,
+ alias: String,
+ storePassword: String,
+ password: String,
+ keystoreType: String
+ }, {}, options.argv, 0);
-function findOutputApksHelper(dir, build_type, arch) {
- var ret = findApks(dir).filter(function(candidate) {
- // Need to choose between release and debug .apk.
- if (build_type === 'debug') {
- return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
- }
- if (build_type === 'release') {
- return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
- }
- return true;
- });
- ret = sortFilesByDate(ret);
- if (ret.length === 0) {
- return ret;
- }
- // Assume arch-specific build if newest apk has -x86 or -arm.
- var archSpecific = !!/-x86|-arm/.exec(ret[0]);
- // And show only arch-specific ones (or non-arch-specific)
- ret = ret.filter(function(p) {
- /*jshint -W018 */
- return !!/-x86|-arm/.exec(p) == archSpecific;
- /*jshint +W018 */
- });
- if (archSpecific && ret.length > 1) {
- ret = ret.filter(function(p) {
- return p.indexOf('-' + arch) != -1;
- });
- }
-
- return ret;
-}
-
-function hasCustomRules() {
- return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
-}
-
-function extractRealProjectNameFromManifest(projectPath) {
- var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, 'utf8');
- var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
- if (!m) {
- throw new Error('Could not find package name in ' + manifestPath);
- }
-
- var packageName=m[1];
- var lastDotIndex = packageName.lastIndexOf('.');
- return packageName.substring(lastDotIndex + 1);
-}
-
-function extractProjectNameFromManifest(projectPath) {
- var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
- var manifestData = fs.readFileSync(manifestPath, 'utf8');
- var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
- if (!m) {
- throw new Error('Could not find activity name in ' + manifestPath);
- }
- return m[1];
-}
-
-function findAllUniq(data, r) {
- var s = {};
- var m;
- while ((m = r.exec(data))) {
- s[m[1]] = 1;
- }
- return Object.keys(s);
-}
-
-function readProjectProperties() {
- var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
- return {
- libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
- gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
- systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+ var ret = {
+ buildType: options.release ? 'release' : 'debug',
+ buildMethod: process.env.ANDROID_BUILD || 'gradle',
+ prepEnv: options.argv.prepenv,
+ arch: resolvedTarget && resolvedTarget.arch,
+ extraArgs: []
};
-}
-
-var builders = {
- ant: {
- getArgs: function(cmd, opts) {
- var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
- // custom_rules.xml is required for incremental builds.
- if (hasCustomRules()) {
- args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
- }
- if(opts.packageInfo) {
- args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
- }
- return args;
- },
- prepEnv: function(opts) {
- return check_reqs.check_ant()
- .then(function() {
- // Copy in build.xml on each build so that:
- // A) we don't require the Android SDK at project creation time, and
- // B) we always use the SDK's latest version of it.
- var sdkDir = process.env['ANDROID_HOME'];
- var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
- function writeBuildXml(projectPath) {
- var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
- fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
- if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
- fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
- }
- }
- writeBuildXml(ROOT);
- var propertiesObj = readProjectProperties();
- var subProjects = propertiesObj.libs;
- for (var i = 0; i < subProjects.length; ++i) {
- writeBuildXml(path.join(ROOT, subProjects[i]));
- }
- if (propertiesObj.systemLibs.length > 0) {
- throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
- }
+ if (options.argv.ant || options.argv.gradle)
+ ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
- var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
- var propertiesFilePath = path.join(ROOT, propertiesFile);
- if (opts.packageInfo) {
- fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
- } else if(isAutoGenerated(propertiesFilePath)) {
- shell.rm('-f', propertiesFilePath);
- }
- });
- },
+ if (options.nobuild) ret.buildMethod = 'none';
- /*
- * Builds the project with ant.
- * Returns a promise.
- */
- build: function(opts) {
- // Without our custom_rules.xml, we need to clean before building.
- var ret = Q();
- if (!hasCustomRules()) {
- // clean will call check_ant() for us.
- ret = this.clean(opts);
- }
+ if (options.argv.versionCode)
+ ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
- var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
- return check_reqs.check_ant()
- .then(function() {
- console.log('Executing: ant ' + args.join(' '));
- return spawn('ant', args);
- });
- },
-
- clean: function(opts) {
- var args = this.getArgs('clean', opts);
- return check_reqs.check_ant()
- .then(function() {
- return spawn('ant', args);
- });
- },
+ if (options.argv.minSdkVersion)
+ ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
- findOutputApks: function(build_type) {
- var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
- return findOutputApksHelper(binDir, build_type, null);
- }
- },
- gradle: {
- getArgs: function(cmd, opts) {
- if (cmd == 'release') {
- cmd = 'cdvBuildRelease';
- } else if (cmd == 'debug') {
- cmd = 'cdvBuildDebug';
- }
- var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
- if (opts.arch) {
- args.push('-PcdvBuildArch=' + opts.arch);
- }
-
- // 10 seconds -> 6 seconds
- args.push('-Dorg.gradle.daemon=true');
- args.push.apply(args, opts.extraArgs);
- // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
- // args.push('-Dorg.gradle.parallel=true');
- return args;
- },
-
- // Makes the project buildable, minus the gradle wrapper.
- prepBuildFiles: function() {
- var projectPath = ROOT;
- // Update the version of build.gradle in each dependent library.
- var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
- var propertiesObj = readProjectProperties();
- var subProjects = propertiesObj.libs;
- for (var i = 0; i < subProjects.length; ++i) {
- if (subProjects[i] !== 'CordovaLib') {
- shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
- }
- }
-
- var name = extractRealProjectNameFromManifest(ROOT);
- //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
- var settingsGradlePaths = subProjects.map(function(p){
- var realDir=p.replace(/[/\\]/g, ':');
- var libName=realDir.replace(name+'-','');
- var str='include ":'+libName+'"\n';
- if(realDir.indexOf(name+'-')!==-1)
- str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
- return str;
- });
-
- // Write the settings.gradle file.
- fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
- '// GENERATED FILE - DO NOT EDIT\n' +
- 'include ":"\n' + settingsGradlePaths.join(''));
- // Update dependencies within build.gradle.
- var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
- var depsList = '';
- subProjects.forEach(function(p) {
- var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
- depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
- depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
- });
- // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
- var SYSTEM_LIBRARY_MAPPINGS = [
- [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
- [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
- ];
- propertiesObj.systemLibs.forEach(function(p) {
- var mavenRef;
- // It's already in gradle form if it has two ':'s
- if (/:.*:/.exec(p)) {
- mavenRef = p;
- } else {
- for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
- var pair = SYSTEM_LIBRARY_MAPPINGS[i];
- if (pair[0].exec(p)) {
- mavenRef = p.replace(pair[0], pair[1]);
- break;
- }
- }
- if (!mavenRef) {
- throw new Error('Unsupported system library (does not work with gradle): ' + p);
- }
- }
- depsList += ' compile "' + mavenRef + '"\n';
- });
- buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
- var includeList = '';
- propertiesObj.gradleIncludes.forEach(function(includePath) {
- includeList += 'apply from: "' + includePath + '"\n';
- });
- buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
- fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
- },
-
- prepEnv: function(opts) {
- var self = this;
- return check_reqs.check_gradle()
- .then(function() {
- return self.prepBuildFiles();
- }).then(function() {
- // Copy the gradle wrapper on each build so that:
- // A) we don't require the Android SDK at project creation time, and
- // B) we always use the SDK's latest version of it.
- var projectPath = ROOT;
- // check_reqs ensures that this is set.
- var sdkDir = process.env['ANDROID_HOME'];
- var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
- if (process.platform == 'win32') {
- shell.rm('-f', path.join(projectPath, 'gradlew.bat'));
- shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath);
- } else {
- shell.rm('-f', path.join(projectPath, 'gradlew'));
- shell.cp(path.join(wrapperDir, 'gradlew'), projectPath);
- }
- shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
- shell.mkdir('-p', 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 we want.
- // 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.
- // For some reason, using ^ and $ don't work. This does the job, though.
- var distributionUrlRegex = /distributionUrl.*zip/;
- var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
- var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
- shell.chmod('u+w', gradleWrapperPropertiesPath);
- shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
-
- var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
- var propertiesFilePath = path.join(ROOT, propertiesFile);
- if (opts.packageInfo) {
- fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
- } else if (isAutoGenerated(propertiesFilePath)) {
- shell.rm('-f', propertiesFilePath);
- }
- });
- },
-
- /*
- * Builds the project with gradle.
- * Returns a promise.
- */
- build: function(opts) {
- var wrapper = path.join(ROOT, 'gradlew');
- var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
- return Q().then(function() {
- console.log('Running: ' + wrapper + ' ' + args.join(' '));
- return spawn(wrapper, args);
- });
- },
-
- clean: function(opts) {
- var builder = this;
- var wrapper = path.join(ROOT, 'gradlew');
- var args = builder.getArgs('clean', opts);
- return Q().then(function() {
- console.log('Running: ' + wrapper + ' ' + args.join(' '));
- return spawn(wrapper, args);
- });
- },
-
- findOutputApks: function(build_type, arch) {
- var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
- return findOutputApksHelper(binDir, build_type, arch);
- }
- },
-
- none: {
- prepEnv: function() {
- return Q();
- },
- build: function() {
- console.log('Skipping build...');
- return Q(null);
- },
- clean: function() {
- return Q();
- },
- findOutputApks: function(build_type, arch) {
- return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
- }
+ if (options.argv.gradleArg) {
+ ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
}
-};
-module.exports.isBuildFlag = function(flag) {
- return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
-};
+ var packageArgs = {};
-function parseOpts(options, resolvedTarget) {
- // Backwards-compatibility: Allow a single string argument
- if (typeof options == 'string') options = [options];
+ if (options.argv.keystore)
+ packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
- var ret = {
- buildType: 'debug',
- buildMethod: process.env['ANDROID_BUILD'] || 'gradle',
- arch: null,
- extraArgs: []
- };
+ ['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
+ if (options.argv[flagName])
+ packageArgs[flagName] = options.argv[flagName];
+ });
- var multiValueArgs = {
- 'versionCode': true,
- 'minSdkVersion': true,
- 'gradleArg': true,
- 'keystore' : true,
- 'alias' : true,
- 'password' : true,
- 'storePassword' : true,
- 'keystoreType' : true,
- 'buildConfig' : true
- };
- var packageArgs = {};
- var buildConfig;
- // Iterate through command line options
- for (var i=0; options && (i < options.length); ++i) {
- if (/^--/.exec(options[i])) {
- var keyValue = options[i].substring(2).split('=');
- var flagName = keyValue.shift();
- var flagValue = keyValue.join('=');
- if (multiValueArgs[flagName] && !flagValue) {
- flagValue = options[i + 1];
- ++i;
- }
- switch(flagName) {
- case 'debug':
- case 'release':
- ret.buildType = flagName;
- break;
- case 'ant':
- case 'gradle':
- ret.buildMethod = flagName;
- break;
- case 'device':
- case 'emulator':
- // Don't need to do anything special to when building for device vs emulator.
- // iOS uses this flag to switch on architecture.
- break;
- case 'prepenv' :
- ret.prepEnv = true;
- break;
- case 'nobuild' :
- ret.buildMethod = 'none';
- break;
- case 'versionCode':
- ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
- break;
- case 'minSdkVersion':
- ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
- break;
- case 'gradleArg':
- ret.extraArgs.push(flagValue);
- break;
- case 'keystore':
- packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
- break;
- case 'alias':
- case 'storePassword':
- case 'password':
- case 'keystoreType':
- packageArgs[flagName] = flagValue;
- break;
- case 'buildConfig':
- buildConfig = flagValue;
- break;
- default :
- console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
- }
- } else {
- console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
- }
- }
+ var buildConfig = options.buildConfig;
// If some values are not specified as command line arguments - use build config to supplement them.
// Command line arguemnts have precedence over build config.
@@ -497,16 +88,17 @@ function parseOpts(options, resolvedTarget) {
if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig);
}
- console.log('Reading build config file: '+ path.resolve(buildConfig));
- var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
+ events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig));
+ var buildjson = fs.readFileSync(buildConfig, 'utf8');
+ var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
if (config.android && config.android[ret.buildType]) {
var androidInfo = config.android[ret.buildType];
if(androidInfo.keystore && !packageArgs.keystore) {
- if(path.isAbsolute(androidInfo.keystore)) {
- packageArgs.keystore = androidInfo.keystore;
- } else {
- packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
+ if(androidInfo.keystore.substr(0,1) === '~') {
+ androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
}
+ packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
+ events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
}
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
@@ -514,6 +106,7 @@ function parseOpts(options, resolvedTarget) {
});
}
}
+
if (packageArgs.keystore && packageArgs.alias) {
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
packageArgs.password, packageArgs.keystoreType);
@@ -521,10 +114,9 @@ function parseOpts(options, resolvedTarget) {
if(!ret.packageInfo) {
if(Object.keys(packageArgs).length > 0) {
- console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
+ events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
}
}
- ret.arch = resolvedTarget && resolvedTarget.arch;
return ret;
}
@@ -534,41 +126,39 @@ function parseOpts(options, resolvedTarget) {
* Returns a promise.
*/
module.exports.runClean = function(options) {
- var opts = parseOpts(options);
- var builder = builders[opts.buildMethod];
+ var opts = parseOpts(options, null, this.root);
+ var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
return builder.clean(opts);
- }).then(function() {
- shell.rm('-rf', path.join(ROOT, 'out'));
-
- ['debug', 'release'].forEach(function(config) {
- var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
- if(isAutoGenerated(propertiesFilePath)){
- shell.rm('-f', propertiesFilePath);
- }
- });
});
};
-/*
- * Builds the project with the specifed options
- * Returns a promise.
+/**
+ * Builds the project with the specifed options.
+ *
+ * @param {BuildOptions} options A set of options. See PlatformApi.build
+ * method documentation for reference.
+ * @param {Object} optResolvedTarget A deployment target. Used to pass
+ * target architecture from upstream 'run' call. TODO: remove this option in
+ * favor of setting buildOptions.archs field.
+ *
+ * @return {Promise<Object>} Promise, resolved with built packages
+ * information.
*/
module.exports.run = function(options, optResolvedTarget) {
- var opts = parseOpts(options, optResolvedTarget);
- var builder = builders[opts.buildMethod];
+ var opts = parseOpts(options, optResolvedTarget, this.root);
+ var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts)
.then(function() {
if (opts.prepEnv) {
- console.log('Build file successfully prepared.');
+ events.emit('verbose', 'Build file successfully prepared.');
return;
}
return builder.build(opts)
.then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
- console.log('Built the following apk(s):');
- console.log(' ' + apkPaths.join('\n '));
+ events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
@@ -578,46 +168,38 @@ module.exports.run = function(options, optResolvedTarget) {
});
};
-// Called by plugman after installing plugins, and by create script after creating project.
-module.exports.prepBuildFiles = function() {
- var builder = builders['gradle'];
- return builder.prepBuildFiles();
-};
-
/*
* Detects the architecture of a device/emulator
* Returns "arm" or "x86".
*/
module.exports.detectArchitecture = function(target) {
function helper() {
- return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
+ return Adb.shell(target, 'cat /proc/cpuinfo')
.then(function(output) {
- if (/intel/i.exec(output)) {
- return 'x86';
- }
- return 'arm';
+ return /intel/i.exec(output) ? 'x86' : 'arm';
});
}
// It sometimes happens (at least on OS X), that this command will hang forever.
// To fix it, either unplug & replug device, or restart adb server.
- return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
+ return helper()
+ .timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
.then(null, function(err) {
if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
- return exec('killall adb')
+ events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
+ return spawn('killall', ['adb'])
.then(function() {
- console.log('adb seems hung. retrying.');
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac.
- console.log('Now device not found... restarting adb again.');
- return exec('killall adb')
+ events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
+ return spawn('killall', ['adb'])
.then(function() {
return helper()
.then(null, function() {
- return Q.reject('USB is flakey. Try unplugging & replugging the device.');
+ return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
});
});
});
@@ -632,16 +214,18 @@ module.exports.detectArchitecture = function(target) {
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) {
+ var apkName = path.basename(p);
if (buildResults.buildType == 'debug') {
- return /-debug/.exec(p);
+ return /-debug/.exec(apkName);
}
- return !/-debug/.exec(p);
+ return !/-debug/.exec(apkName);
});
var archPattern = new RegExp('-' + arch);
var hasArchPattern = /-x86|-arm/;
for (var i = 0; i < paths.length; ++i) {
- if (hasArchPattern.exec(paths[i])) {
- if (archPattern.exec(paths[i])) {
+ var apkName = path.basename(paths[i]);
+ if (hasArchPattern.exec(apkName)) {
+ if (archPattern.exec(apkName)) {
return paths[i];
}
} else {
@@ -695,7 +279,7 @@ PackageInfo.prototype = {
};
module.exports.help = function() {
- console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
+ console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)');
console.log(' \'--release\': will build project for release');
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js
new file mode 100644
index 00000000..4e0f71ab
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/AntBuilder.js
@@ -0,0 +1,156 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var SIGNING_PROPERTIES = '-signing.properties';
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var TEMPLATE =
+ '# This file is automatically generated.\n' +
+ '# Do not modify this file -- ' + MARKER + '\n';
+
+var GenericBuilder = require('./GenericBuilder');
+
+function AntBuilder (projectRoot) {
+ GenericBuilder.call(this, projectRoot);
+
+ this.binDirs = {ant: this.binDirs.ant};
+}
+
+util.inherits(AntBuilder, GenericBuilder);
+
+AntBuilder.prototype.getArgs = function(cmd, opts) {
+ var args = [cmd, '-f', path.join(this.root, 'build.xml')];
+ // custom_rules.xml is required for incremental builds.
+ if (hasCustomRules(this.root)) {
+ args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
+ }
+ if(opts.packageInfo) {
+ args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES));
+ }
+ return args;
+};
+
+AntBuilder.prototype.prepEnv = function(opts) {
+ var self = this;
+ return check_reqs.check_ant()
+ .then(function() {
+ // Copy in build.xml on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ /*jshint -W069 */
+ var sdkDir = process.env['ANDROID_HOME'];
+ /*jshint +W069 */
+ var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
+ function writeBuildXml(projectPath) {
+ var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
+ fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
+ if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
+ fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
+ }
+ }
+ writeBuildXml(self.root);
+ var propertiesObj = self.readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ for (var i = 0; i < subProjects.length; ++i) {
+ writeBuildXml(path.join(self.root, subProjects[i]));
+ }
+ if (propertiesObj.systemLibs.length > 0) {
+ throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Use gradle instead.');
+ }
+
+ var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+ var propertiesFilePath = path.join(self.root, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if(isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+};
+
+/*
+ * Builds the project with ant.
+ * Returns a promise.
+ */
+AntBuilder.prototype.build = function(opts) {
+ // Without our custom_rules.xml, we need to clean before building.
+ var ret = Q();
+ if (!hasCustomRules(this.root)) {
+ // clean will call check_ant() for us.
+ ret = this.clean(opts);
+ }
+
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+ return check_reqs.check_ant()
+ .then(function() {
+ return spawn('ant', args, {stdio: 'pipe'});
+ }).progress(function (stdio){
+ if (stdio.stderr) {
+ process.stderr.write(stdio.stderr);
+ } else {
+ process.stdout.write(stdio.stdout);
+ }
+ }).catch(function (error) {
+ if (error.toString().indexOf('Unable to resolve project target') >= 0) {
+ return check_reqs.check_android_target(error).then(function() {
+ // If due to some odd reason - check_android_target succeeds
+ // we should still fail here.
+ return Q.reject(error);
+ });
+ }
+ return Q.reject(error);
+ });
+};
+
+AntBuilder.prototype.clean = function(opts) {
+ var args = this.getArgs('clean', opts);
+ var self = this;
+ return check_reqs.check_ant()
+ .then(function() {
+ return spawn('ant', args, {stdio: 'inherit'});
+ })
+ .then(function () {
+ shell.rm('-rf', path.join(self.root, 'out'));
+
+ ['debug', 'release'].forEach(function(config) {
+ var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
+ if(isAutoGenerated(propertiesFilePath)){
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ });
+};
+
+module.exports = AntBuilder;
+
+function hasCustomRules(projectRoot) {
+ return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+function isAutoGenerated(file) {
+ return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js
new file mode 100644
index 00000000..362da431
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/GenericBuilder.js
@@ -0,0 +1,147 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+function GenericBuilder (projectDir) {
+ this.root = projectDir || path.resolve(__dirname, '../../..');
+ this.binDirs = {
+ ant: path.join(this.root, hasCustomRules(this.root) ? 'ant-build' : 'bin'),
+ gradle: path.join(this.root, 'build', 'outputs', 'apk')
+ };
+}
+
+function hasCustomRules(projectRoot) {
+ return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+GenericBuilder.prototype.prepEnv = function() {
+ return Q();
+};
+
+GenericBuilder.prototype.build = function() {
+ events.emit('log', 'Skipping build...');
+ return Q(null);
+};
+
+GenericBuilder.prototype.clean = function() {
+ return Q();
+};
+
+GenericBuilder.prototype.findOutputApks = function(build_type, arch) {
+ var self = this;
+ return Object.keys(this.binDirs)
+ .reduce(function (result, builderName) {
+ var binDir = self.binDirs[builderName];
+ return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
+ }, [])
+ .sort(apkSorter);
+};
+
+GenericBuilder.prototype.readProjectProperties = function () {
+ function findAllUniq(data, r) {
+ var s = {};
+ var m;
+ while ((m = r.exec(data))) {
+ s[m[1]] = 1;
+ }
+ return Object.keys(s);
+ }
+
+ var data = fs.readFileSync(path.join(this.root, 'project.properties'), 'utf8');
+ return {
+ libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
+ gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
+ systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+ };
+};
+
+GenericBuilder.prototype.extractRealProjectNameFromManifest = function () {
+ var manifestPath = path.join(this.root, 'AndroidManifest.xml');
+ var manifestData = fs.readFileSync(manifestPath, 'utf8');
+ var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
+ if (!m) {
+ throw new CordovaError('Could not find package name in ' + manifestPath);
+ }
+
+ var packageName=m[1];
+ var lastDotIndex = packageName.lastIndexOf('.');
+ return packageName.substring(lastDotIndex + 1);
+};
+
+module.exports = GenericBuilder;
+
+function apkSorter(fileA, fileB) {
+ // De-prioritize unsigned builds
+ var unsignedRE = /-unsigned/;
+ if (unsignedRE.exec(fileA)) {
+ return 1;
+ } else if (unsignedRE.exec(fileB)) {
+ return -1;
+ }
+
+ var timeDiff = fs.statSync(fileA).mtime - fs.statSync(fileB).mtime;
+ return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
+}
+
+function findOutputApksHelper(dir, build_type, arch) {
+ var shellSilent = shell.config.silent;
+ shell.config.silent = true;
+
+ var ret = shell.ls(path.join(dir, '*.apk'))
+ .filter(function(candidate) {
+ var apkName = path.basename(candidate);
+ // Need to choose between release and debug .apk.
+ if (build_type === 'debug') {
+ return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
+ }
+ if (build_type === 'release') {
+ return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
+ }
+ return true;
+ })
+ .sort(apkSorter);
+
+ shellSilent = shellSilent;
+
+ if (ret.length === 0) {
+ return ret;
+ }
+ // Assume arch-specific build if newest apk has -x86 or -arm.
+ var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
+ // And show only arch-specific ones (or non-arch-specific)
+ ret = ret.filter(function(p) {
+ /*jshint -W018 */
+ return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific;
+ /*jshint +W018 */
+ });
+
+ if (archSpecific && ret.length > 1 && arch) {
+ ret = ret.filter(function(p) {
+ return path.basename(p).indexOf('-' + arch) != -1;
+ });
+ }
+
+ return ret;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js
new file mode 100644
index 00000000..f415646e
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js
@@ -0,0 +1,266 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var GenericBuilder = require('./GenericBuilder');
+
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var SIGNING_PROPERTIES = '-signing.properties';
+var TEMPLATE =
+ '# This file is automatically generated.\n' +
+ '# Do not modify this file -- ' + MARKER + '\n';
+
+function GradleBuilder (projectRoot) {
+ GenericBuilder.call(this, projectRoot);
+
+ this.binDirs = {gradle: this.binDirs.gradle};
+}
+
+util.inherits(GradleBuilder, GenericBuilder);
+
+GradleBuilder.prototype.getArgs = function(cmd, opts) {
+ if (cmd == 'release') {
+ cmd = 'cdvBuildRelease';
+ } else if (cmd == 'debug') {
+ cmd = 'cdvBuildDebug';
+ }
+ var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
+ if (opts.arch) {
+ args.push('-PcdvBuildArch=' + opts.arch);
+ }
+
+ // 10 seconds -> 6 seconds
+ args.push('-Dorg.gradle.daemon=true');
+ // to allow dex in process
+ args.push('-Dorg.gradle.jvmargs=-Xmx2048m');
+ // allow NDK to be used - required by Gradle 1.5 plugin
+ args.push('-Pandroid.useDeprecatedNdk=true');
+ args.push.apply(args, opts.extraArgs);
+ // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
+ // args.push('-Dorg.gradle.parallel=true');
+ return args;
+};
+
+// Makes the project buildable, minus the gradle wrapper.
+GradleBuilder.prototype.prepBuildFiles = function() {
+ // Update the version of build.gradle in each dependent library.
+ var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
+ var propertiesObj = this.readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ var checkAndCopy = function(subProject, root) {
+ var subProjectGradle = path.join(root, subProject, 'build.gradle');
+ // This is the future-proof way of checking if a file exists
+ // This must be synchronous to satisfy a Travis test
+ try {
+ fs.accessSync(subProjectGradle, fs.F_OK);
+ } catch (e) {
+ shell.cp('-f', pluginBuildGradle, subProjectGradle);
+ }
+ };
+ for (var i = 0; i < subProjects.length; ++i) {
+ if (subProjects[i] !== 'CordovaLib') {
+ checkAndCopy(subProjects[i], this.root);
+ }
+ }
+ var name = this.extractRealProjectNameFromManifest();
+ //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
+ var settingsGradlePaths = subProjects.map(function(p){
+ var realDir=p.replace(/[/\\]/g, ':');
+ var libName=realDir.replace(name+'-','');
+ var str='include ":'+libName+'"\n';
+ if(realDir.indexOf(name+'-')!==-1)
+ str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
+ return str;
+ });
+
+ // Write the settings.gradle file.
+ fs.writeFileSync(path.join(this.root, 'settings.gradle'),
+ '// GENERATED FILE - DO NOT EDIT\n' +
+ 'include ":"\n' + settingsGradlePaths.join(''));
+ // Update dependencies within build.gradle.
+ var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
+ var depsList = '';
+ var root = this.root;
+ var insertExclude = function(p) {
+ var gradlePath = path.join(root, p, 'build.gradle');
+ var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
+ if(projectGradleFile.indexOf('CordovaLib') != -1) {
+ depsList += '{\n exclude module:("CordovaLib")\n }\n';
+ }
+ else {
+ depsList +='\n';
+ }
+ };
+ subProjects.forEach(function(p) {
+ console.log('Subproject Path: ' + p);
+ var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
+ depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))';
+ insertExclude(p);
+ depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))';
+ insertExclude(p);
+ });
+ // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
+ var SYSTEM_LIBRARY_MAPPINGS = [
+ [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
+ [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
+ ];
+ propertiesObj.systemLibs.forEach(function(p) {
+ var mavenRef;
+ // It's already in gradle form if it has two ':'s
+ if (/:.*:/.exec(p)) {
+ mavenRef = p;
+ } else {
+ for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
+ var pair = SYSTEM_LIBRARY_MAPPINGS[i];
+ if (pair[0].exec(p)) {
+ mavenRef = p.replace(pair[0], pair[1]);
+ break;
+ }
+ }
+ if (!mavenRef) {
+ throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
+ }
+ }
+ depsList += ' compile "' + mavenRef + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
+ var includeList = '';
+ propertiesObj.gradleIncludes.forEach(function(includePath) {
+ includeList += 'apply from: "' + includePath + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
+ fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
+};
+
+GradleBuilder.prototype.prepEnv = function(opts) {
+ var self = this;
+ return check_reqs.check_gradle()
+ .then(function() {
+ return self.prepBuildFiles();
+ }).then(function() {
+ // Copy the gradle wrapper on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ // check_reqs ensures that this is set.
+ /*jshint -W069 */
+ var sdkDir = process.env['ANDROID_HOME'];
+ /*jshint +W069 */
+ var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
+ if (process.platform == 'win32') {
+ shell.rm('-f', path.join(self.root, 'gradlew.bat'));
+ shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
+ } else {
+ shell.rm('-f', path.join(self.root, 'gradlew'));
+ shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
+ }
+ shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
+ shell.mkdir('-p', path.join(self.root, 'gradle'));
+ shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
+
+ // If the gradle distribution URL is set, make sure it points to version we want.
+ // 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.
+ // For some reason, using ^ and $ don't work. This does the job, though.
+ var distributionUrlRegex = /distributionUrl.*zip/;
+ /*jshint -W069 */
+ var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-2.14.1-all.zip';
+ /*jshint +W069 */
+ var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
+ shell.chmod('u+w', gradleWrapperPropertiesPath);
+ shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath);
+
+ var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+ var propertiesFilePath = path.join(self.root, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if (isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+};
+
+/*
+ * Builds the project with gradle.
+ * Returns a promise.
+ */
+GradleBuilder.prototype.build = function(opts) {
+ var wrapper = path.join(this.root, 'gradlew');
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+
+ return spawn(wrapper, args, {stdio: 'pipe'})
+ .progress(function (stdio){
+ if (stdio.stderr) {
+ /*
+ * Workaround for the issue with Java printing some unwanted information to
+ * stderr instead of stdout.
+ * This function suppresses 'Picked up _JAVA_OPTIONS' message from being
+ * printed to stderr. See https://issues.apache.org/jira/browse/CB-9971 for
+ * explanation.
+ */
+ var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
+ if (suppressThisLine) {
+ return;
+ }
+ process.stderr.write(stdio.stderr);
+ } else {
+ process.stdout.write(stdio.stdout);
+ }
+ }).catch(function (error) {
+ if (error.toString().indexOf('failed to find target with hash string') >= 0) {
+ return check_reqs.check_android_target(error).then(function() {
+ // If due to some odd reason - check_android_target succeeds
+ // we should still fail here.
+ return Q.reject(error);
+ });
+ }
+ return Q.reject(error);
+ });
+};
+
+GradleBuilder.prototype.clean = function(opts) {
+ var builder = this;
+ var wrapper = path.join(this.root, 'gradlew');
+ var args = builder.getArgs('clean', opts);
+ return Q().then(function() {
+ return spawn(wrapper, args, {stdio: 'inherit'});
+ })
+ .then(function () {
+ shell.rm('-rf', path.join(builder.root, 'out'));
+
+ ['debug', 'release'].forEach(function(config) {
+ var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
+ if(isAutoGenerated(propertiesFilePath)){
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ });
+};
+
+module.exports = GradleBuilder;
+
+function isAutoGenerated(file) {
+ return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/builders/builders.js b/StoneIsland/platforms/android/cordova/lib/builders/builders.js
new file mode 100644
index 00000000..4921c49a
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/builders/builders.js
@@ -0,0 +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.
+*/
+
+var CordovaError = require('cordova-common').CordovaError;
+
+var knownBuilders = {
+ ant: 'AntBuilder',
+ gradle: 'GradleBuilder',
+ none: 'GenericBuilder'
+};
+
+/**
+ * Helper method that instantiates and returns a builder for specified build
+ * type.
+ *
+ * @param {String} builderType Builder name to construct and return. Must
+ * be one of 'ant', 'gradle' or 'none'
+ *
+ * @return {Builder} A builder instance for specified build type.
+ */
+module.exports.getBuilder = function (builderType, projectRoot) {
+ if (!knownBuilders[builderType])
+ throw new CordovaError('Builder ' + builderType + ' is not supported.');
+
+ try {
+ var Builder = require('./' + knownBuilders[builderType]);
+ return new Builder(projectRoot);
+ } catch (err) {
+ throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
+ }
+};
diff --git a/StoneIsland/platforms/android/cordova/lib/check_reqs.js b/StoneIsland/platforms/android/cordova/lib/check_reqs.js
index 9d251596..ac6fa4c1 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/check_reqs.js
+++ b/StoneIsland/platforms/android/cordova/lib/check_reqs.js
@@ -26,15 +26,14 @@ var shelljs = require('shelljs'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
- which = require('which'),
ROOT = path.join(__dirname, '..', '..');
+var CordovaError = require('cordova-common').CordovaError;
var isWindows = process.platform == 'win32';
function forgivingWhichSync(cmd) {
try {
- // TODO: Should use shelljs.which() here to have one less dependency.
- return fs.realpathSync(which.sync(cmd));
+ return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
@@ -43,7 +42,7 @@ function forgivingWhichSync(cmd) {
function tryCommand(cmd, errMsg, catchStderr) {
var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) {
- if (err) d.reject(new Error(errMsg));
+ if (err) d.reject(new CordovaError(errMsg));
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example
else d.resolve((catchStderr ? stderr : stdout).trim());
@@ -83,12 +82,12 @@ module.exports.check_ant = function() {
module.exports.check_gradle = function() {
var sdkDir = process.env['ANDROID_HOME'];
if (!sdkDir)
- return Q.reject('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
- 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.');
+ return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
+ 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (!fs.existsSync(wrapperDir)) {
- return Q.reject(new Error('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
+ return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
'Looked here: ' + wrapperDir));
}
return Q.when();
@@ -120,7 +119,7 @@ module.exports.check_java = function() {
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
} else {
- throw new Error(msg);
+ throw new CordovaError(msg);
}
}
} else if (isWindows) {
@@ -143,22 +142,21 @@ module.exports.check_java = function() {
}
}
}).then(function() {
- var msg =
- 'Failed to run "java -version", make sure that you have a JDK installed.\n' +
- 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
- if (process.env['JAVA_HOME']) {
- msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
- }
- return tryCommand('java -version', msg)
- .then(function() {
+ var msg =
+ 'Failed to run "javac -version", make sure that you have a JDK installed.\n' +
+ 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
+ if (process.env['JAVA_HOME']) {
+ msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
+ }
// We use tryCommand with catchStderr = true, because
// javac writes version info to stderr instead of stdout
- return tryCommand('javac -version', msg, true);
- }).then(function (output) {
- var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
- return match && match[1];
+ return tryCommand('javac -version', msg, true)
+ .then(function (output) {
+ //Let's check for at least Java 8, and keep it future proof so we can support Java 10
+ var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output);
+ return match && match[1];
+ });
});
- });
};
// Returns a promise.
@@ -212,7 +210,7 @@ module.exports.check_android = function() {
process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true;
} else {
- throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
+ throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.');
}
@@ -221,27 +219,32 @@ module.exports.check_android = function() {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
}
if (!process.env['ANDROID_HOME']) {
- throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
+ throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
}
if (!fs.existsSync(process.env['ANDROID_HOME'])) {
- throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
+ throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
'\nTry update it manually to point to valid SDK directory.');
}
+ return hasAndroidHome;
});
};
-module.exports.getAbsoluteAndroidCmd = function() {
- return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
+module.exports.getAbsoluteAndroidCmd = function () {
+ var cmd = forgivingWhichSync('android');
+ if (process.platform === 'win32') {
+ return '"' + cmd + '"';
+ }
+ return cmd.replace(/(\s)/g, '\\$1');
};
-module.exports.check_android_target = function(valid_target) {
+module.exports.check_android_target = function(originalError) {
// valid_target can look like:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
- if (!valid_target) valid_target = module.exports.get_target();
+ var valid_target = module.exports.get_target();
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) {
@@ -251,24 +254,38 @@ module.exports.check_android_target = function(valid_target) {
}
var androidCmd = module.exports.getAbsoluteAndroidCmd();
- throw new Error('Please install Android target: "' + valid_target + '".\n\n' +
+ var msg = '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)');
+ '3. "Android SDK Build-tools" (latest)';
+ if (originalError) {
+ msg = originalError + '\n' + msg;
+ }
+ throw new CordovaError(msg);
});
};
// Returns a promise.
module.exports.run = function() {
- return Q.all([this.check_java(), this.check_android().then(this.check_android_target)])
- .then(function() {
- console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
- console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
- });
+ return Q.all([this.check_java(), this.check_android()])
+ .then(function(values) {
+ console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
+ console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
+
+ if (!values[0]) {
+ throw new CordovaError('Requirements check failed for JDK 1.8 or greater');
+ }
+
+
+ if (!values[1]) {
+ throw new CordovaError('Requirements check failed for Android SDK');
+ }
+ });
};
+
/**
* Object thar represents one of requirements for current platform.
* @param {String} id The unique identifier for this requirements.
diff --git a/StoneIsland/platforms/android/cordova/lib/device.js b/StoneIsland/platforms/android/cordova/lib/device.js
index c13fdc40..4b171db6 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/device.js
+++ b/StoneIsland/platforms/android/cordova/lib/device.js
@@ -19,40 +19,30 @@
under the License.
*/
-var exec = require('./exec'),
- Q = require('q'),
- os = require('os'),
- build = require('./build'),
- appinfo = require('./appinfo');
+var Q = require('q'),
+ build = require('./build');
+var path = require('path');
+var Adb = require('./Adb');
+var AndroidManifest = require('./AndroidManifest');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var events = require('cordova-common').events;
/**
* Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found.
*/
module.exports.list = function(lookHarder) {
- function helper() {
- return exec('adb devices', os.tmpdir())
- .then(function(output) {
- var response = output.split('\n');
- var device_list = [];
- for (var i = 1; i < response.length; i++) {
- if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) {
- device_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
- }
- }
- return device_list;
- });
- }
- return helper()
+ return Adb.devices()
.then(function(list) {
if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines.
- return exec('killall adb')
+ return spawn('killall', ['adb'])
.then(function() {
- console.log('Restarting adb to see if more devices are detected.');
- return helper();
+ events.emit('verbose', 'Restarting adb to see if more devices are detected.');
+ return Adb.devices();
}, function() {
// For non-killall OS's.
return list;
@@ -66,7 +56,7 @@ module.exports.resolveTarget = function(target) {
return this.list(true)
.then(function(device_list) {
if (!device_list || !device_list.length) {
- return Q.reject('ERROR: Failed to deploy to device, no devices found.');
+ return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
@@ -95,27 +85,36 @@ module.exports.install = function(target, buildResults) {
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, os.tmpdir())
- .then(function(output) {
- if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
+ var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+ var pkgName = manifest.getPackageId();
+ var launchName = pkgName + '/.' + manifest.getActivity().getName();
+ events.emit('log', 'Using apk: ' + apk_path);
+ events.emit('log', 'Package name: ' + pkgName);
- //unlock screen
- var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82';
- return exec(cmd, os.tmpdir());
- }, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); })
+ return Adb.install(resolvedTarget.target, apk_path, {replace: true})
+ .catch(function (error) {
+ // CB-9557 CB-10157 only uninstall and reinstall app if the one that
+ // is already installed on device was signed w/different certificate
+ if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
+ throw error;
+
+ events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
+ 'installed app already signed with different key');
+
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Adb.uninstall(resolvedTarget.target, pkgName)
+ .then(function() {
+ return Adb.install(resolvedTarget.target, apk_path, {replace: true});
+ });
+ })
.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, os.tmpdir());
+ //unlock screen
+ return Adb.shell(resolvedTarget.target, 'input keyevent 82');
+ }).then(function() {
+ return Adb.start(resolvedTarget.target, launchName);
}).then(function() {
- console.log('LAUNCH SUCCESS');
- }, function(err) {
- return Q.reject('ERROR: Failed to launch application on device: ' + err);
+ events.emit('log', 'LAUNCH SUCCESS');
});
});
};
diff --git a/StoneIsland/platforms/android/cordova/lib/emulator.js b/StoneIsland/platforms/android/cordova/lib/emulator.js
index e81dd679..ff1e261c 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/emulator.js
+++ b/StoneIsland/platforms/android/cordova/lib/emulator.js
@@ -21,11 +21,14 @@
/* jshint sub:true */
-var exec = require('./exec');
-var appinfo = require('./appinfo');
var retry = require('./retry');
var build = require('./build');
-var check_reqs = require('./check_reqs');
+var path = require('path');
+var Adb = require('./Adb');
+var AndroidManifest = require('./AndroidManifest');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
var Q = require('q');
var os = require('os');
@@ -33,8 +36,10 @@ var child_process = require('child_process');
// constants
var ONE_SECOND = 1000; // in milliseconds
-var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds
+var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
+var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3;
+var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
var EXEC_KILL_SIGNAL = 'SIGKILL';
/**
@@ -48,7 +53,7 @@ var EXEC_KILL_SIGNAL = 'SIGKILL';
}
*/
module.exports.list_images = function() {
- return exec('android list avds')
+ return spawn('android', ['list', 'avds'])
.then(function(output) {
var response = output.split('\n');
var emulator_list = [];
@@ -57,13 +62,18 @@ module.exports.list_images = function() {
var img_obj = {};
if (response[i].match(/Name:\s/)) {
img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
+ if (response[i + 1].match(/Device:\s/)) {
+ i++;
+ img_obj['device'] = response[i].split('Device: ')[1].replace('\r', '');
+ }
if (response[i + 1].match(/Path:\s/)) {
i++;
img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
}
- if (response[i + 1].match(/\(API\slevel\s/)) {
+ if (response[i + 1].match(/\(API\slevel\s/) || (response[i + 2] && response[i + 2].match(/\(API\slevel\s/))) {
i++;
- img_obj['target'] = response[i].replace('\r', '');
+ var secondLine = response[i + 1].match(/\(API\slevel\s/) ? response[i + 1] : '';
+ img_obj['target'] = (response[i] + secondLine).split('Target: ')[1].replace('\r', '');
}
if (response[i + 1].match(/ABI:\s/)) {
i++;
@@ -92,11 +102,15 @@ module.exports.list_images = function() {
* Returns a promise.
*/
module.exports.best_image = function() {
- var project_target = check_reqs.get_target().replace('android-', '');
return this.list_images()
.then(function(images) {
+ // Just return undefined if there is no images
+ if (images.length === 0) return;
+
var closest = 9999;
var best = images[0];
+ // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue
+ var project_target = require('./check_reqs').get_target().replace('android-', '');
for (var i in images) {
var target = images[i].target;
if(target) {
@@ -115,22 +129,12 @@ module.exports.best_image = function() {
// Returns a promise.
module.exports.list_started = function() {
- return exec('adb devices', os.tmpdir())
- .then(function(output) {
- var response = output.split('\n');
- var started_emulator_list = [];
- for (var i = 1; i < response.length; i++) {
- if (response[i].match(/device/) && response[i].match(/emulator/)) {
- started_emulator_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
- }
- }
- return started_emulator_list;
- });
+ return Adb.devices({emulators: true});
};
// Returns a promise.
module.exports.list_targets = function() {
- return exec('android list targets', os.tmpdir())
+ return spawn('android', ['list', 'targets'], {cwd: os.tmpdir()})
.then(function(output) {
var target_out = output.split('\n');
var targets = [];
@@ -144,108 +148,139 @@ module.exports.list_targets = function() {
};
/*
+ * Gets unused port for android emulator, between 5554 and 5584
+ * Returns a promise.
+ */
+module.exports.get_available_port = function () {
+ var self = this;
+
+ return self.list_started()
+ .then(function (emulators) {
+ for (var p = 5584; p >= 5554; p-=2) {
+ if (emulators.indexOf('emulator-' + p) === -1) {
+ events.emit('verbose', 'Found available port: ' + p);
+ return p;
+ }
+ }
+ throw new CordovaError('Could not find an available avd port');
+ });
+};
+
+/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
- * If no ID is given it will used the first image available,
+ * If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?).
+ * If no boot timeout is given or the value is negative it will wait forever for
+ * the emulator to boot
*
* Returns a promise.
*/
-module.exports.start = function(emulator_ID) {
+module.exports.start = function(emulator_ID, boot_timeout) {
var self = this;
- var emulator_id, num_started, started_emulators;
- return self.list_started()
- .then(function(list) {
- started_emulators = list;
- num_started = started_emulators.length;
- if (!emulator_ID) {
- return self.list_images()
- .then(function(emulator_list) {
- if (emulator_list.length > 0) {
- return self.best_image()
- .then(function(best) {
- emulator_ID = best.name;
- console.log('WARNING : no emulator specified, defaulting to ' + emulator_ID);
- return emulator_ID;
- });
- } else {
- var androidCmd = check_reqs.getAbsoluteAndroidCmd();
- return Q.reject('ERROR : No emulator images (avds) found.\n' +
- '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
- '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
- 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n');
- }
- });
- } else {
- return Q(emulator_ID);
- }
- }).then(function() {
- var cmd = 'emulator';
- var args = ['-avd', emulator_ID];
- var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
- proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
- }).then(function() {
- // wait for emulator to start
- console.log('Waiting for emulator...');
- return self.wait_for_emulator(num_started);
- }).then(function(new_started) {
- if (new_started.length > 1) {
- for (var i in new_started) {
- if (started_emulators.indexOf(new_started[i]) < 0) {
- emulator_id = new_started[i];
- }
+ return Q().then(function() {
+ if (emulator_ID) return Q(emulator_ID);
+
+ return self.best_image()
+ .then(function(best) {
+ if (best && best.name) {
+ events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
+ return best.name;
}
- } else {
- emulator_id = new_started[0];
- }
- if (!emulator_id) return Q.reject('ERROR : Failed to start emulator, could not find new emulator');
- //wait for emulator to boot up
- process.stdout.write('Booting up emulator (this may take a while)...');
- return self.wait_for_boot(emulator_id);
- }).then(function() {
- console.log('BOOT COMPLETE');
+ // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue
+ var androidCmd = require('./check_reqs').getAbsoluteAndroidCmd();
+ return Q.reject(new CordovaError('No emulator images (avds) found.\n' +
+ '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
+ '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
+ 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
+ });
+ }).then(function(emulatorId) {
+ return self.get_available_port()
+ .then(function (port) {
+ var args = ['-avd', emulatorId, '-port', port];
+ // Don't wait for it to finish, since the emulator will probably keep running for a long time.
+ child_process
+ .spawn('emulator', args, { stdio: 'inherit', detached: true })
+ .unref();
- //unlock screen
- return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir());
- }).then(function() {
- //return the new emulator id for the started emulators
- return emulator_id;
+ // wait for emulator to start
+ events.emit('log', 'Waiting for emulator to start...');
+ return self.wait_for_emulator(port);
+ });
+ }).then(function(emulatorId) {
+ if (!emulatorId)
+ return Q.reject(new CordovaError('Failed to start emulator'));
+
+ //wait for emulator to boot up
+ process.stdout.write('Waiting for emulator to boot (this may take a while)...');
+ return self.wait_for_boot(emulatorId, boot_timeout)
+ .then(function(success) {
+ if (success) {
+ events.emit('log','BOOT COMPLETE');
+ //unlock screen
+ return Adb.shell(emulatorId, 'input keyevent 82')
+ .then(function() {
+ //return the new emulator id for the started emulators
+ return emulatorId;
+ });
+ } else {
+ // We timed out waiting for the boot to happen
+ return null;
+ }
+ });
});
};
/*
- * Waits for the new emulator to apear on the started-emulator list.
- * Returns a promise with a list of newly started emulators' IDs.
+ * Waits for an emulator to boot on a given port.
+ * Returns this emulator's ID in a promise.
*/
-module.exports.wait_for_emulator = function(num_running) {
+module.exports.wait_for_emulator = function(port) {
var self = this;
- return self.list_started()
- .then(function(new_started) {
- if (new_started.length > num_running) {
- return new_started;
- } else {
- return Q.delay(1000).then(function() {
- return self.wait_for_emulator(num_running);
- });
- }
- });
+ return Q().then(function() {
+ var emulator_id = 'emulator-' + port;
+ return Adb.shell(emulator_id, 'getprop dev.bootcomplete')
+ .then(function (output) {
+ if (output.indexOf('1') >= 0) {
+ return emulator_id;
+ }
+ return self.wait_for_emulator(port);
+ }, function (error) {
+ if (error && error.message &&
+ (error.message.indexOf('not found') > -1) ||
+ error.message.indexOf('device offline') > -1) {
+ // emulator not yet started, continue waiting
+ return self.wait_for_emulator(port);
+ } else {
+ // something unexpected has happened
+ throw error;
+ }
+ });
+ });
};
/*
- * Waits for the boot animation property of the emulator to switch to 'stopped'
+ * Waits for the core android process of the emulator to start. Returns a
+ * promise that resolves to a boolean indicating success. Not specifying a
+ * time_remaining or passing a negative value will cause it to wait forever
*/
-module.exports.wait_for_boot = function(emulator_id) {
+module.exports.wait_for_boot = function(emulator_id, time_remaining) {
var self = this;
- return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir())
+ return Adb.shell(emulator_id, 'ps')
.then(function(output) {
- if (output.match(/stopped/)) {
- return;
+ if (output.match(/android\.process\.acore/)) {
+ return true;
+ } else if (time_remaining === 0) {
+ return false;
} else {
process.stdout.write('.');
- return Q.delay(3000).then(function() {
- return self.wait_for_boot(emulator_id);
+
+ // Check at regular intervals
+ return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function() {
+ var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
+ return self.wait_for_boot(emulator_id, updated_time);
});
}
});
@@ -257,9 +292,9 @@ module.exports.wait_for_boot = function(emulator_id) {
* Returns a promise.
*/
module.exports.create_image = function(name, target) {
- console.log('Creating avd named ' + name);
+ console.log('Creating new avd named ' + name);
if (target) {
- return exec('android create avd --name ' + name + ' --target ' + target)
+ return spawn('android', ['create', 'avd', '--name', name, '--target', target])
.then(null, function(error) {
console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?');
@@ -267,11 +302,11 @@ module.exports.create_image = function(name, target) {
});
} else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
- return exec('android create avd --name ' + name + ' --target ' + this.list_targets()[0])
+ return spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]])
.then(function() {
// TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.');
- console.error('Please insure you have targets available by running the "android" command');
+ console.error('Ensure you have targets available by running the "android" command');
return Q.reject();
}, function(error) {
console.error('ERROR : Failed to create emulator image : ');
@@ -284,7 +319,7 @@ module.exports.resolveTarget = function(target) {
return this.list_started()
.then(function(emulator_list) {
if (emulator_list.length < 1) {
- return Q.reject('No started emulators found, please start an emultor before deploying your project.');
+ return Q.reject('No running Android emulators found, please start an emulator before deploying your project.');
}
// default emulator
@@ -309,6 +344,8 @@ module.exports.resolveTarget = function(target) {
module.exports.install = function(givenTarget, buildResults) {
var target;
+ var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
+ var pkgName = manifest.getPackageId();
// resolve the target emulator
return Q().then(function () {
@@ -324,49 +361,83 @@ module.exports.install = function(givenTarget, buildResults) {
// install the app
}).then(function () {
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Q.when()
+ .then(function() {
- var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
- var execOptions = {
- timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
- killSignal: EXEC_KILL_SIGNAL
- };
+ var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
+ var execOptions = {
+ cwd: os.tmpdir(),
+ timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
+ killSignal: EXEC_KILL_SIGNAL
+ };
- console.log('Installing app on emulator...');
- console.log('Using apk: ' + apk_path);
+ events.emit('log', 'Using apk: ' + apk_path);
+ events.emit('log', 'Package name: ' + pkgName);
+ events.emit('verbose', 'Installing app on emulator...');
- var retriedInstall = retry.retryPromise(
- NUM_INSTALL_RETRIES,
- exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(), execOptions
- );
+ // A special function to call adb install in specific environment w/ specific options.
+ // Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
+ // to workaround sporadic emulator hangs
+ function adbInstallWithOptions(target, apk, opts) {
+ events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
- return retriedInstall.then(function (output) {
- if (output.match(/Failure/)) {
- return Q.reject('Failed to install apk to emulator: ' + output);
- } else {
- console.log('INSTALL SUCCESS');
+ var command = 'adb -s ' + target + ' install -r "' + apk + '"';
+ return Q.promise(function (resolve, reject) {
+ child_process.exec(command, opts, function(err, stdout, stderr) {
+ if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
+ // adb does not return an error code even if installation fails. Instead it puts a specific
+ // message to stdout, so we have to use RegExp matching to detect installation failure.
+ else if (/Failure/.test(stdout)) {
+ if (stdout.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
+ stdout += 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
+ ' or sign and deploy the unsigned apk manually using Android tools.';
+ } else if (stdout.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
+ stdout += 'You\'re trying to install apk with a lower versionCode that is already installed.' +
+ '\nEither uninstall an app or increment the versionCode.';
+ }
+
+ reject(new CordovaError('Failed to install apk to emulator: ' + stdout));
+ } else resolve(stdout);
+ });
+ });
}
- }, function (err) {
- return Q.reject('Failed to install apk to emulator: ' + err);
- });
- // unlock screen
- }).then(function () {
+ function installPromise () {
+ return adbInstallWithOptions(target.target, apk_path, execOptions)
+ .catch(function (error) {
+ // CB-9557 CB-10157 only uninstall and reinstall app if the one that
+ // is already installed on device was signed w/different certificate
+ if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString()))
+ throw error;
- console.log('Unlocking screen...');
- return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir());
+ events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
+ 'currently installed app was signed with different key');
- // launch the application
- }).then(function () {
+ // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
+ // or the app doesn't installed at all, so no error catching needed.
+ return Adb.uninstall(target.target, pkgName)
+ .then(function() {
+ return adbInstallWithOptions(target.target, apk_path, execOptions);
+ });
+ });
+ }
- console.log('Launching application...');
- var launchName = appinfo.getActivityName();
- var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
- return exec(cmd, os.tmpdir());
+ return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise)
+ .then(function (output) {
+ events.emit('log', 'INSTALL SUCCESS');
+ });
+ });
+ // unlock screen
+ }).then(function () {
+ events.emit('verbose', 'Unlocking screen...');
+ return Adb.shell(target.target, 'input keyevent 82');
+ }).then(function () {
+ Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure
}).then(function (output) {
- console.log('LAUNCH SUCCESS');
- }, function (err) {
- return Q.reject('Failed to launch app on emulator: ' + err);
+ events.emit('log', 'LAUNCH SUCCESS');
});
};
diff --git a/StoneIsland/platforms/android/cordova/lib/exec.js b/StoneIsland/platforms/android/cordova/lib/exec.js
deleted file mode 100755
index 798a93ba..00000000
--- a/StoneIsland/platforms/android/cordova/lib/exec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var child_process = require("child_process");
-var Q = require("q");
-
-// constants
-var DEFAULT_MAX_BUFFER = 1024000;
-
-// Takes a command and optional current working directory.
-// Returns a promise that either resolves with the stdout, or
-// rejects with an error message and the stderr.
-//
-// WARNING:
-// opt_cwd is an artifact of an old design, and must
-// be removed in the future; the correct solution is
-// to pass the options object the same way that
-// child_process.exec expects
-//
-// NOTE:
-// exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
-module.exports = function(cmd, opt_cwd, options) {
-
- var d = Q.defer();
-
- if (typeof options === "undefined") {
- options = {};
- }
-
- // override cwd to preserve old opt_cwd behavior
- options.cwd = opt_cwd;
-
- // set maxBuffer
- if (typeof options.maxBuffer === "undefined") {
- options.maxBuffer = DEFAULT_MAX_BUFFER;
- }
-
- try {
- child_process.exec(cmd, options, function(err, stdout, stderr) {
- if (err) d.reject("Error executing \"" + cmd + "\": " + stderr);
- else d.resolve(stdout);
- });
- } catch(e) {
- console.error("error caught: " + e);
- d.reject(e);
- }
-
- return d.promise;
-};
-
diff --git a/StoneIsland/platforms/android/cordova/lib/install-device.bat b/StoneIsland/platforms/android/cordova/lib/install-device.bat
index ac7214ac..ac7214ac 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/install-device.bat
+++ b/StoneIsland/platforms/android/cordova/lib/install-device.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/install-emulator.bat b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
index 1ec67790..1ec67790 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
+++ b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices b/StoneIsland/platforms/android/cordova/lib/list-devices
index e390bff6..fa84d7f6 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-devices
+++ b/StoneIsland/platforms/android/cordova/lib/list-devices
@@ -22,12 +22,13 @@
var devices = require('./device');
// Usage support for when args are given
-devices.list().done(function(device_list) {
- device_list && device_list.forEach(function(dev) {
- console.log(dev);
+require('../lib/check_reqs').check_android().then(function() {
+ devices.list().done(function(device_list) {
+ device_list && device_list.forEach(function(dev) {
+ console.log(dev);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
-
diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices.bat b/StoneIsland/platforms/android/cordova/lib/list-devices.bat
index c0bcdd9a..c0bcdd9a 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-devices.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-devices.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images b/StoneIsland/platforms/android/cordova/lib/list-emulator-images
index 996cf555..03c827fe 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-emulator-images
+++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images
@@ -22,11 +22,13 @@
var emulators = require('./emulator');
// Usage support for when args are given
-emulators.list_images().done(function(emulator_list) {
- emulator_list && emulator_list.forEach(function(emu) {
- console.log(emu.name);
+require('../lib/check_reqs').check_android().then(function() {
+ emulators.list_images().done(function(emulator_list) {
+ emulator_list && emulator_list.forEach(function(emu) {
+ console.log(emu.name);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
index 661cbf95..661cbf95 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators b/StoneIsland/platforms/android/cordova/lib/list-started-emulators
index 2ae8c5a8..a890dec6 100755
--- a/StoneIsland/platforms/android/cordova/lib/list-started-emulators
+++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators
@@ -22,11 +22,13 @@
var emulators = require('./emulator');
// Usage support for when args are given
-emulators.list_started().done(function(emulator_list) {
- emulator_list && emulator_list.forEach(function(emu) {
- console.log(emu);
+require('../lib/check_reqs').check_android().then(function() {
+ emulators.list_started().done(function(emulator_list) {
+ emulator_list && emulator_list.forEach(function(emu) {
+ console.log(emu);
+ });
+ }, function(err) {
+ console.error('ERROR: ' + err);
+ process.exit(2);
});
-}, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
});
diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
index a4e88f7d..a4e88f7d 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
+++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat
diff --git a/StoneIsland/platforms/android/cordova/lib/log.js b/StoneIsland/platforms/android/cordova/lib/log.js
index ebf836d5..ebf836d5 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/log.js
+++ b/StoneIsland/platforms/android/cordova/lib/log.js
diff --git a/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
index b345b90a..d1c63365 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
+++ b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle
@@ -21,28 +21,19 @@
buildscript {
repositories {
mavenCentral()
+ jcenter()
}
// Switch the Android Gradle plugin version requirement depending on the
// installed version of Gradle. This dependency is documented at
// http://tools.android.com/tech-docs/new-build-system/version-compatibility
// and https://issues.apache.org/jira/browse/CB-8143
- if (gradle.gradleVersion >= "2.2") {
- dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0+'
- }
- } else if (gradle.gradleVersion >= "2.1") {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.14.0+'
- }
- } else {
- dependencies {
- classpath 'com.android.tools.build:gradle:0.12.0+'
- }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0+'
}
}
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
diff --git a/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js b/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js
new file mode 100644
index 00000000..5e745fd5
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/pluginHandlers.js
@@ -0,0 +1,308 @@
+/*
+ *
+ * Copyright 2013 Anis Kadri
+ *
+ * Licensed 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 unused: vars */
+
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+var handlers = {
+ 'source-file':{
+ install:function(obj, plugin, project, options) {
+ if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id));
+ if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id));
+
+ var dest = path.join(obj.targetDir, path.basename(obj.src));
+
+ if(options && options.android_studio === true) {
+ dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+ }
+
+ if (options && options.force) {
+ copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ } else {
+ copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var dest = path.join(obj.targetDir, path.basename(obj.src));
+
+ if(options && options.android_studio === true) {
+ dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
+ }
+
+ deleteJava(project.projectDir, dest);
+ }
+ },
+ 'lib-file':{
+ install:function(obj, plugin, project, options) {
+ var dest = path.join('libs', path.basename(obj.src));
+ if(options && options.android_studio === true) {
+ dest = path.join('app/libs', path.basename(obj.src));
+ }
+ copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var dest = path.join('libs', path.basename(obj.src));
+ if(options && options.android_studio === true) {
+ dest = path.join('app/libs', path.basename(obj.src));
+ }
+ removeFile(project.projectDir, dest);
+ }
+ },
+ 'resource-file':{
+ install:function(obj, plugin, project, options) {
+ copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link));
+ },
+ uninstall:function(obj, plugin, project, options) {
+ removeFile(project.projectDir, path.normalize(obj.target));
+ }
+ },
+ 'framework': {
+ install:function(obj, plugin, project, options) {
+ var src = obj.src;
+ if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
+
+ events.emit('verbose', 'Installing Android library: ' + src);
+ var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
+ var subDir;
+
+ if (obj.custom) {
+ var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
+ copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, !!(options && options.link));
+ subDir = path.resolve(project.projectDir, subRelativeDir);
+ } else {
+ obj.type = 'sys';
+ subDir = src;
+ }
+
+ if (obj.type == 'gradleReference') {
+ project.addGradleReference(parentDir, subDir);
+ } else if (obj.type == 'sys') {
+ project.addSystemLibrary(parentDir, subDir);
+ } else {
+ project.addSubProject(parentDir, subDir);
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var src = obj.src;
+ if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
+
+ events.emit('verbose', 'Uninstalling Android library: ' + src);
+ var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
+ var subDir;
+
+ if (obj.custom) {
+ var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
+ removeFile(project.projectDir, subRelativeDir);
+ subDir = path.resolve(project.projectDir, subRelativeDir);
+ // If it's the last framework in the plugin, remove the parent directory.
+ var parDir = path.dirname(subDir);
+ if (fs.existsSync(parDir) && fs.readdirSync(parDir).length === 0) {
+ fs.rmdirSync(parDir);
+ }
+ } else {
+ obj.type = 'sys';
+ subDir = src;
+ }
+
+ if (obj.type == 'gradleReference') {
+ project.removeGradleReference(parentDir, subDir);
+ } else if (obj.type == 'sys') {
+ project.removeSystemLibrary(parentDir, subDir);
+ } else {
+ project.removeSubProject(parentDir, subDir);
+ }
+ }
+ },
+ asset:{
+ install:function(obj, plugin, project, options) {
+ if (!obj.src) {
+ throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
+ }
+ if (!obj.target) {
+ throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+ }
+
+ copyFile(plugin.dir, obj.src, project.www, obj.target);
+ if (options && options.usePlatformWww) {
+ // CB-11022 copy file to both directories if usePlatformWww is specified
+ copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var target = obj.target || obj.src;
+
+ if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+
+ removeFileF(path.resolve(project.www, target));
+ removeFileF(path.resolve(project.www, 'plugins', plugin.id));
+ if (options && options.usePlatformWww) {
+ // CB-11022 remove file from both directories if usePlatformWww is specified
+ removeFileF(path.resolve(project.platformWww, target));
+ removeFileF(path.resolve(project.platformWww, 'plugins', plugin.id));
+ }
+ }
+ },
+ 'js-module': {
+ install: function (obj, plugin, project, options) {
+ // Copy the plugin's files into the www directory.
+ var moduleSource = path.resolve(plugin.dir, obj.src);
+ var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
+
+ // Read in the file, prepend the cordova.define, and write it back out.
+ var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+ if (moduleSource.match(/.*\.json$/)) {
+ scriptContent = 'module.exports = ' + scriptContent;
+ }
+ scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
+
+ var wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(wwwDest));
+ fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
+
+ if (options && options.usePlatformWww) {
+ // CB-11022 copy file to both directories if usePlatformWww is specified
+ var platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(platformWwwDest));
+ fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
+ }
+ },
+ uninstall: function (obj, plugin, project, options) {
+ var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+ removeFileAndParents(project.www, pluginRelativePath);
+ if (options && options.usePlatformWww) {
+ // CB-11022 remove file from both directories if usePlatformWww is specified
+ removeFileAndParents(project.platformWww, pluginRelativePath);
+ }
+ }
+ }
+};
+
+module.exports.getInstaller = function (type) {
+ if (handlers[type] && handlers[type].install) {
+ return handlers[type].install;
+ }
+
+ events.emit('verbose', '<' + type + '> is not supported for android plugins');
+};
+
+module.exports.getUninstaller = function(type) {
+ if (handlers[type] && handlers[type].uninstall) {
+ return handlers[type].uninstall;
+ }
+
+ events.emit('verbose', '<' + type + '> is not supported for android plugins');
+};
+
+function copyFile (plugin_dir, src, project_dir, dest, link) {
+ src = path.resolve(plugin_dir, src);
+ if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
+
+ // check that src path is inside plugin directory
+ var real_path = fs.realpathSync(src);
+ var real_plugin_path = fs.realpathSync(plugin_dir);
+ if (real_path.indexOf(real_plugin_path) !== 0)
+ throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
+
+ dest = path.resolve(project_dir, dest);
+
+ // check that dest path is located in project directory
+ if (dest.indexOf(project_dir) !== 0)
+ throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
+
+ shell.mkdir('-p', path.dirname(dest));
+ if (link) {
+ symlinkFileOrDirTree(src, dest);
+ } else if (fs.statSync(src).isDirectory()) {
+ // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
+ shell.cp('-Rf', src+'/*', dest);
+ } else {
+ shell.cp('-f', src, dest);
+ }
+}
+
+// Same as copy file but throws error if target exists
+function copyNewFile (plugin_dir, src, project_dir, dest, link) {
+ var target_path = path.resolve(project_dir, dest);
+ if (fs.existsSync(target_path))
+ throw new CordovaError('"' + target_path + '" already exists!');
+
+ copyFile(plugin_dir, src, project_dir, dest, !!link);
+}
+
+function symlinkFileOrDirTree(src, dest) {
+ if (fs.existsSync(dest)) {
+ shell.rm('-Rf', dest);
+ }
+
+ if (fs.statSync(src).isDirectory()) {
+ shell.mkdir('-p', dest);
+ fs.readdirSync(src).forEach(function(entry) {
+ symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
+ });
+ }
+ else {
+ fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
+ }
+}
+
+// checks if file exists and then deletes. Error if doesn't exist
+function removeFile (project_dir, src) {
+ var file = path.resolve(project_dir, src);
+ shell.rm('-Rf', file);
+}
+
+// deletes file/directory without checking
+function removeFileF (file) {
+ shell.rm('-Rf', file);
+}
+
+// Sometimes we want to remove some java, and prune any unnecessary empty directories
+function deleteJava (project_dir, destFile) {
+ removeFileAndParents(project_dir, destFile, 'src');
+}
+
+function removeFileAndParents (baseDir, destFile, stopper) {
+ stopper = stopper || '.';
+ var file = path.resolve(baseDir, destFile);
+ if (!fs.existsSync(file)) return;
+
+ removeFileF(file);
+
+ // check if directory is empty
+ var curDir = path.dirname(file);
+
+ while(curDir !== path.resolve(baseDir, stopper)) {
+ if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
+ fs.rmdirSync(curDir);
+ curDir = path.resolve(curDir, '..');
+ } else {
+ // directory not empty...do nothing
+ break;
+ }
+ }
+}
+
+function generateAttributeError(attribute, element, id) {
+ return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/prepare.js b/StoneIsland/platforms/android/cordova/lib/prepare.js
new file mode 100644
index 00000000..10a69ea3
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/prepare.js
@@ -0,0 +1,431 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var AndroidManifest = require('./AndroidManifest');
+var xmlHelpers = require('cordova-common').xmlHelpers;
+var CordovaError = require('cordova-common').CordovaError;
+var ConfigParser = require('cordova-common').ConfigParser;
+var FileUpdater = require('cordova-common').FileUpdater;
+var PlatformJson = require('cordova-common').PlatformJson;
+var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+
+module.exports.prepare = function (cordovaProject, options) {
+ var self = this;
+
+ var platformJson = PlatformJson.load(this.locations.root, this.platform);
+ var munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider());
+
+ this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations);
+
+ // Update own www dir with project's www assets and plugins' assets and js-files
+ return Q.when(updateWww(cordovaProject, this.locations))
+ .then(function () {
+ // update project according to config.xml changes.
+ return updateProjectAccordingTo(self._config, self.locations);
+ })
+ .then(function () {
+ updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
+ updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
+ })
+ .then(function () {
+ events.emit('verbose', 'Prepared android project successfully');
+ });
+};
+
+module.exports.clean = function (options) {
+ // A cordovaProject isn't passed into the clean() function, because it might have
+ // been called from the platform shell script rather than the CLI. Check for the
+ // noPrepare option passed in by the non-CLI clean script. If that's present, or if
+ // there's no config.xml found at the project root, then don't clean prepared files.
+ var projectRoot = path.resolve(this.root, '../..');
+ if ((options && options.noPrepare) || !fs.existsSync(this.locations.configXml) ||
+ !fs.existsSync(this.locations.configXml)) {
+ return Q();
+ }
+
+ var projectConfig = new ConfigParser(this.locations.configXml);
+
+ var self = this;
+ return Q().then(function () {
+ cleanWww(projectRoot, self.locations);
+ cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
+ cleanSplashes(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
+ });
+};
+
+/**
+ * Updates config files in project based on app's config.xml and config munge,
+ * generated by plugins.
+ *
+ * @param {ConfigParser} sourceConfig A project's configuration that will
+ * be merged into platform's config.xml
+ * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
+ * for this platform.
+ * @param {Object} locations A map of locations for this platform
+ *
+ * @return {ConfigParser} An instance of ConfigParser, that
+ * represents current project's configuration. When returned, the
+ * configuration is already dumped to appropriate config.xml file.
+ */
+function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
+ events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml);
+
+ // First cleanup current config and merge project's one into own
+ // Overwrite platform config.xml with defaults.xml.
+ shell.cp('-f', locations.defaultConfigXml, locations.configXml);
+
+ // Then apply config changes from global munge to all config files
+ // in project (including project's config)
+ configMunger.reapply_global_munge().save_all();
+
+ events.emit('verbose', 'Merging project\'s config.xml into platform-specific android config.xml');
+ // Merge changes from app's config.xml into platform's one
+ var config = new ConfigParser(locations.configXml);
+ xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
+ config.doc.getroot(), 'android', /*clobber=*/true);
+
+ config.write();
+ return config;
+}
+
+/**
+ * Logs all file operations via the verbose event stream, indented.
+ */
+function logFileOp(message) {
+ events.emit('verbose', ' ' + message);
+}
+
+/**
+ * Updates platform 'www' directory by replacing it with contents of
+ * 'platform_www' and app www. Also copies project's overrides' folder into
+ * the platform 'www' folder
+ *
+ * @param {Object} cordovaProject An object which describes cordova project.
+ * @param {Object} destinations An object that contains destination
+ * paths for www files.
+ */
+function updateWww(cordovaProject, destinations) {
+ var sourceDirs = [
+ path.relative(cordovaProject.root, cordovaProject.locations.www),
+ path.relative(cordovaProject.root, destinations.platformWww)
+ ];
+
+ // If project contains 'merges' for our platform, use them as another overrides
+ var merges_path = path.join(cordovaProject.root, 'merges', 'android');
+ if (fs.existsSync(merges_path)) {
+ events.emit('verbose', 'Found "merges/android" folder. Copying its contents into the android project.');
+ sourceDirs.push(path.join('merges', 'android'));
+ }
+
+ var targetDir = path.relative(cordovaProject.root, destinations.www);
+ events.emit(
+ 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
+ FileUpdater.mergeAndUpdateDir(
+ sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+/**
+ * Cleans all files from the platform 'www' directory.
+ */
+function cleanWww(projectRoot, locations) {
+ var targetDir = path.relative(projectRoot, locations.www);
+ events.emit('verbose', 'Cleaning ' + targetDir);
+
+ // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
+ FileUpdater.mergeAndUpdateDir(
+ [], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
+}
+
+/**
+ * Updates project structure and AndroidManifest according to project's configuration.
+ *
+ * @param {ConfigParser} platformConfig A project's configuration that will
+ * be used to update project
+ * @param {Object} locations A map of locations for this platform
+ */
+function updateProjectAccordingTo(platformConfig, locations) {
+ // Update app name by editing res/values/strings.xml
+ var name = platformConfig.name();
+ var strings = xmlHelpers.parseElementtreeSync(locations.strings);
+ strings.find('string[@name="app_name"]').text = name.replace(/\'/g, '\\\'');
+ fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8');
+ events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings);
+
+ // Java packages cannot support dashes
+ var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
+
+ var manifest = new AndroidManifest(locations.manifest);
+ var orig_pkg = manifest.getPackageId();
+
+ manifest.getActivity()
+ .setOrientation(platformConfig.getPreference('orientation'))
+ .setLaunchMode(findAndroidLaunchModePreference(platformConfig));
+
+ manifest.setVersionName(platformConfig.version())
+ .setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
+ .setPackageId(pkg)
+ .setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android'))
+ .setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android'))
+ .setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
+ .write();
+
+ var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java');
+ var java_files = shell.ls(javaPattern).filter(function(f) {
+ return shell.grep(/extends\s+CordovaActivity/g, f);
+ });
+
+ if (java_files.length === 0) {
+ throw new CordovaError('No Java files found that extend CordovaActivity.');
+ } else if(java_files.length > 1) {
+ events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]);
+ }
+
+ var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0]));
+ shell.mkdir('-p', path.dirname(destFile));
+ shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile);
+ events.emit('verbose', 'Wrote out Android package name "' + pkg + '" to ' + destFile);
+
+ if (orig_pkg !== pkg) {
+ // If package was name changed we need to remove old java with main activity
+ shell.rm('-Rf',java_files[0]);
+ // remove any empty directories
+ var currentDir = path.dirname(java_files[0]);
+ var sourcesRoot = path.resolve(locations.root, 'src');
+ while(currentDir !== sourcesRoot) {
+ if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
+ fs.rmdirSync(currentDir);
+ currentDir = path.resolve(currentDir, '..');
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+// Consturct the default value for versionCode as
+// PATCH + MINOR * 100 + MAJOR * 10000
+// see http://developer.android.com/tools/publishing/versioning.html
+function default_versionCode(version) {
+ var nums = version.split('-')[0].split('.');
+ var versionCode = 0;
+ if (+nums[0]) {
+ versionCode += +nums[0] * 10000;
+ }
+ if (+nums[1]) {
+ versionCode += +nums[1] * 100;
+ }
+ if (+nums[2]) {
+ versionCode += +nums[2];
+ }
+
+ events.emit('verbose', 'android-versionCode not found in config.xml. Generating a code based on version in config.xml (' + version + '): ' + versionCode);
+ return versionCode;
+}
+
+function getImageResourcePath(resourcesDir, type, density, name, sourceName) {
+ if (/\.9\.png$/.test(sourceName)) {
+ name = name.replace(/\.png$/, '.9.png');
+ }
+ var resourcePath = path.join(resourcesDir, (density ? type + '-' + density : type), name);
+ return resourcePath;
+}
+
+function updateSplashes(cordovaProject, platformResourcesDir) {
+ var resources = cordovaProject.projectConfig.getSplashScreens('android');
+
+ // if there are "splash" elements in config.xml
+ if (resources.length === 0) {
+ events.emit('verbose', 'This app does not have splash screens defined');
+ return;
+ }
+
+ var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'drawable', 'screen.png');
+
+ var hadMdpi = false;
+ resources.forEach(function (resource) {
+ if (!resource.density) {
+ return;
+ }
+ if (resource.density == 'mdpi') {
+ hadMdpi = true;
+ }
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'drawable', resource.density, 'screen.png', path.basename(resource.src));
+ resourceMap[targetPath] = resource.src;
+ });
+
+ // There's no "default" drawable, so assume default == mdpi.
+ if (!hadMdpi && resources.defaultResource) {
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'drawable', 'mdpi', 'screen.png', path.basename(resources.defaultResource.src));
+ resourceMap[targetPath] = resources.defaultResource.src;
+ }
+
+ events.emit('verbose', 'Updating splash screens at ' + platformResourcesDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanSplashes(projectRoot, projectConfig, platformResourcesDir) {
+ var resources = projectConfig.getSplashScreens('android');
+ if (resources.length > 0) {
+ var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
+ events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
+
+ // No source paths are specified in the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+function updateIcons(cordovaProject, platformResourcesDir) {
+ var icons = cordovaProject.projectConfig.getIcons('android');
+
+ // if there are icon elements in config.xml
+ if (icons.length === 0) {
+ events.emit('verbose', 'This app does not have launcher icons defined');
+ return;
+ }
+
+ var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'icon.png');
+
+ var android_icons = {};
+ var default_icon;
+ // http://developer.android.com/design/style/iconography.html
+ var sizeToDensityMap = {
+ 36: 'ldpi',
+ 48: 'mdpi',
+ 72: 'hdpi',
+ 96: 'xhdpi',
+ 144: 'xxhdpi',
+ 192: 'xxxhdpi'
+ };
+ // find the best matching icon for a given density or size
+ // @output android_icons
+ var parseIcon = function(icon, icon_size) {
+ // do I have a platform icon for that density already
+ var density = icon.density || sizeToDensityMap[icon_size];
+ if (!density) {
+ // invalid icon defition ( or unsupported size)
+ return;
+ }
+ var previous = android_icons[density];
+ if (previous && previous.platform) {
+ return;
+ }
+ android_icons[density] = icon;
+ };
+
+ // iterate over all icon elements to find the default icon and call parseIcon
+ for (var i=0; i<icons.length; i++) {
+ var icon = icons[i];
+ var size = icon.width;
+ if (!size) {
+ size = icon.height;
+ }
+ if (!size && !icon.density) {
+ if (default_icon) {
+ events.emit('verbose', 'Found extra default icon: ' + icon.src + ' (ignoring in favor of ' + default_icon.src + ')');
+ } else {
+ default_icon = icon;
+ }
+ } else {
+ parseIcon(icon, size);
+ }
+ }
+
+ // The source paths for icons and splashes are relative to
+ // project's config.xml location, so we use it as base path.
+ for (var density in android_icons) {
+ var targetPath = getImageResourcePath(
+ platformResourcesDir, 'mipmap', density, 'icon.png', path.basename(android_icons[density].src));
+ resourceMap[targetPath] = android_icons[density].src;
+ }
+
+ // There's no "default" drawable, so assume default == mdpi.
+ if (default_icon && !android_icons.mdpi) {
+ var defaultTargetPath = getImageResourcePath(
+ platformResourcesDir, 'mipmap', 'mdpi', 'icon.png', path.basename(default_icon.src));
+ resourceMap[defaultTargetPath] = default_icon.src;
+ }
+
+ events.emit('verbose', 'Updating icons at ' + platformResourcesDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanIcons(projectRoot, projectConfig, platformResourcesDir) {
+ var icons = projectConfig.getIcons('android');
+ if (icons.length > 0) {
+ var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png');
+ events.emit('verbose', 'Cleaning icons at ' + platformResourcesDir);
+
+ // No source paths are specified in the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+/**
+ * Gets a map containing resources of a specified name from all drawable folders in a directory.
+ */
+function mapImageResources(rootDir, subDir, type, resourceName) {
+ var pathMap = {};
+ shell.ls(path.join(rootDir, subDir, type + '-*'))
+ .forEach(function (drawableFolder) {
+ var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
+ pathMap[imagePath] = null;
+ });
+ return pathMap;
+}
+
+/**
+ * Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns
+ * preference value and warns if it doesn't seems to be valid
+ *
+ * @param {ConfigParser} platformConfig A configParser instance for
+ * platform.
+ *
+ * @return {String} Preference's value from config.xml or
+ * default value, if there is no such preference. The default value is
+ * 'singleTop'
+ */
+function findAndroidLaunchModePreference(platformConfig) {
+ var launchMode = platformConfig.getPreference('AndroidLaunchMode');
+ if (!launchMode) {
+ // Return a default value
+ return 'singleTop';
+ }
+
+ var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance'];
+ var valid = expectedValues.indexOf(launchMode) >= 0;
+ if (!valid) {
+ // Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
+ events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' +
+ launchMode + '. Expected values are: ' + expectedValues.join(', '));
+ }
+
+ return launchMode;
+}
diff --git a/StoneIsland/platforms/android/cordova/lib/retry.js b/StoneIsland/platforms/android/cordova/lib/retry.js
index dc52a7d2..3cb49274 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/retry.js
+++ b/StoneIsland/platforms/android/cordova/lib/retry.js
@@ -21,7 +21,9 @@
/* jshint node: true */
-"use strict";
+'use strict';
+
+var events = require('cordova-common').events;
/*
* Retry a promise-returning function a number of times, propagating its
@@ -56,7 +58,7 @@ module.exports.retryPromise = function (attemts_left, promiseFunction) {
throw error;
}
- console.log("A retried call failed. Retrying " + attemts_left + " more time(s).");
+ events.emit('verbose', 'A retried call failed. Retrying ' + attemts_left + ' more time(s).');
// retry call self again with the same arguments, except attemts_left is now lower
var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments);
diff --git a/StoneIsland/platforms/android/cordova/lib/run.js b/StoneIsland/platforms/android/cordova/lib/run.js
index 7f15448c..214a1e19 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/run.js
+++ b/StoneIsland/platforms/android/cordova/lib/run.js
@@ -25,63 +25,36 @@ var path = require('path'),
build = require('./build'),
emulator = require('./emulator'),
device = require('./device'),
- shell = require('shelljs'),
- Q = require('q');
+ Q = require('q'),
+ events = require('cordova-common').events;
-/*
- * Runs the application on a device if available.
- * If no device is found, it will use a started emulator.
- * If no started emulators are found it will attempt to start an avd.
- * If no avds are found it will error out.
- * Returns a promise.
- */
- module.exports.run = function(args) {
- var buildFlags = [];
+function getInstallTarget(runOptions) {
var install_target;
- var list = false;
-
- for (var i=2; i<args.length; i++) {
- if (build.isBuildFlag(args[i])) {
- buildFlags.push(args[i]);
- } else if (args[i] == '--device') {
- install_target = '--device';
- } else if (args[i] == '--emulator') {
- install_target = '--emulator';
- } else if (/^--target=/.exec(args[i])) {
- install_target = args[i].substring(9, args[i].length);
- } else if (args[i] == '--list') {
- list = true;
- } else {
- console.warn('Option \'' + args[i] + '\' not recognized (ignoring).');
- }
+ if (runOptions.target) {
+ install_target = runOptions.target;
+ } else if (runOptions.device) {
+ install_target = '--device';
+ } else if (runOptions.emulator) {
+ install_target = '--emulator';
}
- if (list) {
- var output = '';
- var temp = '';
- if (!install_target) {
- output += 'Available Android Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- output += 'Available Android Virtual Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- } else if (install_target == '--emulator') {
- output += 'Available Android Virtual Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- } else if (install_target == '--device') {
- output += 'Available Android Devices:\n';
- temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output;
- temp = temp.replace(/^(?=[^\s])/gm, '\t');
- output += temp;
- }
- console.log(output);
- return;
- }
+ return install_target;
+}
+
+/**
+ * Runs the application on a device if available. If no device is found, it will
+ * use a started emulator. If no started emulators are found it will attempt
+ * to start an avd. If no avds are found it will error out.
+ *
+ * @param {Object} runOptions various run/build options. See Api.js build/run
+ * methods for reference.
+ *
+ * @return {Promise}
+ */
+ module.exports.run = function(runOptions) {
+
+ var self = this;
+ var install_target = getInstallTarget(runOptions);
return Q()
.then(function() {
@@ -90,10 +63,10 @@ var path = require('path'),
return device.list()
.then(function(device_list) {
if (device_list.length > 0) {
- console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
+ events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
- console.log('WARNING : No target specified, deploying to emulator');
+ events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
@@ -137,17 +110,25 @@ var path = require('path'),
});
});
}).then(function(resolvedTarget) {
- return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
+ // Better just call self.build, but we're doing some processing of
+ // build results (according to platformApi spec) so they are in different
+ // format than emulator.install expects.
+ // TODO: Update emulator/device.install to handle this change
+ return build.run.call(self, runOptions, resolvedTarget)
+ .then(function(buildResults) {
if (resolvedTarget.isEmulator) {
- return emulator.install(resolvedTarget, buildResults);
+ return emulator.wait_for_boot(resolvedTarget.target)
+ .then(function () {
+ return emulator.install(resolvedTarget, buildResults);
+ });
}
return device.install(resolvedTarget, buildResults);
});
});
};
-module.exports.help = function(args) {
- console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]');
+module.exports.help = function() {
+ console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]');
console.log('Build options :');
console.log(' --debug : Builds project in debug mode');
console.log(' --release : Builds project in release mode');
diff --git a/StoneIsland/platforms/android/cordova/lib/spawn.js b/StoneIsland/platforms/android/cordova/lib/spawn.js
deleted file mode 100755
index 3e500a09..00000000
--- a/StoneIsland/platforms/android/cordova/lib/spawn.js
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env node
-
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-var child_process = require('child_process'),
- Q = require('q');
-var isWindows = process.platform.slice(0, 3) == 'win';
-
-// Takes a command and optional current working directory.
-module.exports = function(cmd, args, opt_cwd) {
- var d = Q.defer();
- var opts = { cwd: opt_cwd, stdio: 'inherit' };
- try {
- // Work around spawn not being able to find .bat files.
- if (isWindows) {
- args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')];
- cmd = 'cmd';
- opts.windowsVerbatimArguments = true;
- }
- var child = child_process.spawn(cmd, args, opts);
- child.on('exit', function(code) {
- if (code) {
- d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
- } else {
- d.resolve();
- }
- });
- } catch(e) {
- console.error('error caught: ' + e);
- d.reject(e);
- }
- return d.promise;
-};
diff --git a/StoneIsland/platforms/android/cordova/lib/start-emulator.bat b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat
index 9329d951..9329d951 100755..100644
--- a/StoneIsland/platforms/android/cordova/lib/start-emulator.bat
+++ b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat
diff --git a/StoneIsland/platforms/android/cordova/log.bat b/StoneIsland/platforms/android/cordova/log.bat
index 4b2b434e..4b2b434e 100755..100644
--- a/StoneIsland/platforms/android/cordova/log.bat
+++ b/StoneIsland/platforms/android/cordova/log.bat
diff --git a/StoneIsland/platforms/android/cordova/loggingHelper.js b/StoneIsland/platforms/android/cordova/loggingHelper.js
new file mode 100644
index 00000000..32b2ee0a
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/loggingHelper.js
@@ -0,0 +1,18 @@
+var CordovaLogger = require('cordova-common').CordovaLogger;
+
+module.exports = {
+ adjustLoggerLevel: function (opts) {
+ if (opts instanceof Array) {
+ opts.silent = opts.indexOf('--silent') !== -1;
+ opts.verbose = opts.indexOf('--verbose') !== -1;
+ }
+
+ if (opts.silent) {
+ CordovaLogger.get().setLevel('error');
+ }
+
+ if (opts.verbose) {
+ CordovaLogger.get().setLevel('verbose');
+ }
+ }
+};
diff --git a/StoneIsland/platforms/android/cordova/run b/StoneIsland/platforms/android/cordova/run
index 8c6fe38c..9544c1dc 100755
--- a/StoneIsland/platforms/android/cordova/run
+++ b/StoneIsland/platforms/android/cordova/run
@@ -19,19 +19,35 @@
under the License.
*/
-var run = require('./lib/run'),
- reqs = require('./lib/check_reqs'),
- args = process.argv;
+var Api = require('./Api');
+var nopt = require('nopt');
+var path = require('path');
// Support basic help commands
-if (args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
- args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
- run.help(args);
-} else {
- reqs.run().done(function() {
- return run.run(args);
- }, function(err) {
- console.error('ERROR: ' + err);
- process.exit(2);
- });
-}
+if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
+ require('./lib/run').help();
+
+// Do some basic argument parsing
+var runOpts = nopt({
+ 'verbose' : Boolean,
+ 'silent' : Boolean,
+ 'debug' : Boolean,
+ 'release' : Boolean,
+ 'nobuild': Boolean,
+ 'buildConfig' : path,
+ 'archs' : String,
+ 'device' : Boolean,
+ 'emulator': Boolean,
+ 'target' : String
+}, { 'd' : '--verbose' });
+
+// Make runOptions compatible with PlatformApi run method spec
+runOpts.argv = runOpts.argv.remain;
+
+require('./loggingHelper').adjustLoggerLevel(runOpts);
+
+new Api().run(runOpts)
+.catch(function(err) {
+ console.error(err, err.stack);
+ process.exit(2);
+});
diff --git a/StoneIsland/platforms/android/cordova/run.bat b/StoneIsland/platforms/android/cordova/run.bat
index b0bc28b2..b0bc28b2 100755..100644
--- a/StoneIsland/platforms/android/cordova/run.bat
+++ b/StoneIsland/platforms/android/cordova/run.bat
diff --git a/StoneIsland/platforms/android/cordova/version b/StoneIsland/platforms/android/cordova/version
index 07442655..9bf79a18 100755
--- a/StoneIsland/platforms/android/cordova/version
+++ b/StoneIsland/platforms/android/cordova/version
@@ -20,6 +20,10 @@
*/
// Coho updates this line:
-var VERSION = "4.1.1";
+var VERSION = "6.1.2";
-console.log(VERSION);
+module.exports.version = VERSION;
+
+if (!module.parent) {
+ console.log(VERSION);
+}
diff --git a/StoneIsland/platforms/android/cordova/version.bat b/StoneIsland/platforms/android/cordova/version.bat
index 3610c17b..3610c17b 100755..100644
--- a/StoneIsland/platforms/android/cordova/version.bat
+++ b/StoneIsland/platforms/android/cordova/version.bat
diff --git a/StoneIsland/platforms/android/google-services.json b/StoneIsland/platforms/android/google-services.json
new file mode 100644
index 00000000..57497bf5
--- /dev/null
+++ b/StoneIsland/platforms/android/google-services.json
@@ -0,0 +1,4 @@
+{
+ "project_info": {},
+ "client": []
+}
diff --git a/StoneIsland/platforms/android/phonegap-plugin-push/stoneisland-push.gradle b/StoneIsland/platforms/android/phonegap-plugin-push/stoneisland-push.gradle
new file mode 100644
index 00000000..11e735ae
--- /dev/null
+++ b/StoneIsland/platforms/android/phonegap-plugin-push/stoneisland-push.gradle
@@ -0,0 +1,21 @@
+import java.util.regex.Pattern
+
+def doExtractStringFromManifest(name) {
+ def manifestFile = file(android.sourceSets.main.manifest.srcFile)
+ def pattern = Pattern.compile(name + "=\"(.*?)\"")
+ def matcher = pattern.matcher(manifestFile.getText())
+ matcher.find()
+ return matcher.group(1)
+}
+
+android {
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ }
+ }
+
+ defaultConfig {
+ applicationId = doExtractStringFromManifest("package")
+ }
+}
diff --git a/StoneIsland/platforms/android/platform_www/cordova-js-src/android/nativeapiprovider.js b/StoneIsland/platforms/android/platform_www/cordova-js-src/android/nativeapiprovider.js
index 2e9aa67b..2e9aa67b 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova-js-src/android/nativeapiprovider.js
+++ b/StoneIsland/platforms/android/platform_www/cordova-js-src/android/nativeapiprovider.js
diff --git a/StoneIsland/platforms/android/platform_www/cordova-js-src/android/promptbasednativeapi.js b/StoneIsland/platforms/android/platform_www/cordova-js-src/android/promptbasednativeapi.js
index f7fb6bc7..f7fb6bc7 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova-js-src/android/promptbasednativeapi.js
+++ b/StoneIsland/platforms/android/platform_www/cordova-js-src/android/promptbasednativeapi.js
diff --git a/StoneIsland/platforms/android/platform_www/cordova-js-src/exec.js b/StoneIsland/platforms/android/platform_www/cordova-js-src/exec.js
index fa8b41be..f73d87a1 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova-js-src/exec.js
+++ b/StoneIsland/platforms/android/platform_www/cordova-js-src/exec.js
@@ -51,10 +51,11 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
- ONLINE_EVENT: 2
+ ONLINE_EVENT: 2,
+ EVAL_BRIDGE: 3
},
jsToNativeBridgeMode, // Set lazily.
- nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
+ nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,
bridgeSecret = -1;
@@ -77,6 +78,9 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
+ // If args is not provided, default to an empty array
+ args = args || [];
+
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
@@ -86,7 +90,6 @@ function androidExec(success, fail, service, action, args) {
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
-
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
@@ -106,6 +109,17 @@ function androidExec(success, fail, service, action, args) {
}
androidExec.init = function() {
+ //CB-11828
+ //This failsafe checks the version of Android and if it's Jellybean, it switches it to
+ //using the Online Event bridge for communicating from Native to JS
+ //
+ //It's ugly, but it's necessary.
+ var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
+ var version_code = check && check[0].match(/4.[0-3].*/);
+ if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
+ nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
+ }
+
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
diff --git a/StoneIsland/platforms/android/platform_www/cordova-js-src/platform.js b/StoneIsland/platforms/android/platform_www/cordova-js-src/platform.js
index bffc6751..2bfd0247 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova-js-src/platform.js
+++ b/StoneIsland/platforms/android/platform_www/cordova-js-src/platform.js
@@ -19,6 +19,9 @@
*
*/
+// The last resume event that was received that had the result of a plugin call.
+var lastResumeEvent = null;
+
module.exports = {
id: 'android',
bootstrap: function() {
@@ -58,6 +61,19 @@ module.exports = {
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
+ // The resume event is not "sticky", but it is possible that the event
+ // will contain the result of a plugin call. We need to ensure that the
+ // plugin result is delivered even after the event is fired (CB-10498)
+ var cordovaAddEventListener = document.addEventListener;
+
+ document.addEventListener = function(evt, handler, capture) {
+ cordovaAddEventListener(evt, handler, capture);
+
+ if (evt === 'resume' && lastResumeEvent) {
+ handler(lastResumeEvent);
+ }
+ };
+
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
@@ -79,12 +95,30 @@ function onMessageFromNative(msg) {
case 'searchbutton':
// App life cycle events
case 'pause':
- case 'resume':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
+ case 'resume':
+ if(arguments.length > 1 && msg.pendingResult) {
+ if(arguments.length === 2) {
+ msg.pendingResult.result = arguments[1];
+ } else {
+ // The plugin returned a multipart message
+ var res = [];
+ for(var i = 1; i < arguments.length; i++) {
+ res.push(arguments[i]);
+ }
+ msg.pendingResult.result = res;
+ }
+
+ // Save the plugin result so that it can be delivered to the js
+ // even if they miss the initial firing of the event
+ lastResumeEvent = msg;
+ }
+ cordova.fireDocumentEvent(action, msg);
+ break;
default:
throw new Error('Unknown event action ' + action);
}
diff --git a/StoneIsland/platforms/android/platform_www/cordova-js-src/plugin/android/app.js b/StoneIsland/platforms/android/platform_www/cordova-js-src/plugin/android/app.js
index 22cf96e8..22cf96e8 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova-js-src/plugin/android/app.js
+++ b/StoneIsland/platforms/android/platform_www/cordova-js-src/plugin/android/app.js
diff --git a/StoneIsland/platforms/android/platform_www/cordova.js b/StoneIsland/platforms/android/platform_www/cordova.js
index 23f6e475..18c020e7 100755..100644
--- a/StoneIsland/platforms/android/platform_www/cordova.js
+++ b/StoneIsland/platforms/android/platform_www/cordova.js
@@ -1,5 +1,5 @@
// Platform: android
-// 2c29e187e4206a6a77fba940ef6f77aef5c7eb8c
+// 7c5fcc5a5adfbf3fb8ceaf36fbdd4bd970bd9c20
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License.
*/
;(function() {
-var PLATFORM_VERSION_BUILD_LABEL = '4.1.1';
+var PLATFORM_VERSION_BUILD_LABEL = '6.1.2';
// file: src/scripts/require.js
/*jshint -W079 */
@@ -101,7 +101,9 @@ if (typeof module === "object" && typeof require === "function") {
// file: src/cordova.js
define("cordova", function(require, exports, module) {
-if(window.cordova){
+// Workaround for Windows 10 in hosted environment case
+// http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object
+if (window.cordova && !(window.cordova instanceof HTMLElement)) {
throw new Error("cordova already defined");
}
@@ -740,8 +742,13 @@ var Channel = function(type, sticky) {
}
};
-function forceFunction(f) {
- if (typeof f != 'function') throw "Function required as first argument!";
+function checkSubscriptionArgument(argument) {
+ if (typeof argument !== "function" && typeof argument.handleEvent !== "function") {
+ throw new Error(
+ "Must provide a function or an EventListener object " +
+ "implementing the handleEvent interface."
+ );
+ }
}
/**
@@ -751,28 +758,39 @@ function forceFunction(f) {
* and a guid that can be used to stop subscribing to the channel.
* Returns the guid.
*/
-Channel.prototype.subscribe = function(f, c) {
- // need a function to call
- forceFunction(f);
+Channel.prototype.subscribe = function(eventListenerOrFunction, eventListener) {
+ checkSubscriptionArgument(eventListenerOrFunction);
+ var handleEvent, guid;
+
+ if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
+ // Received an EventListener object implementing the handleEvent interface
+ handleEvent = eventListenerOrFunction.handleEvent;
+ eventListener = eventListenerOrFunction;
+ } else {
+ // Received a function to handle event
+ handleEvent = eventListenerOrFunction;
+ }
+
if (this.state == 2) {
- f.apply(c || this, this.fireArgs);
+ handleEvent.apply(eventListener || this, this.fireArgs);
return;
}
- var func = f,
- guid = f.observer_guid;
- if (typeof c == "object") { func = utils.close(c, f); }
+ guid = eventListenerOrFunction.observer_guid;
+ if (typeof eventListener === "object") {
+ handleEvent = utils.close(eventListener, handleEvent);
+ }
if (!guid) {
- // first time any channel has seen this subscriber
+ // First time any channel has seen this subscriber
guid = '' + nextGuid++;
}
- func.observer_guid = guid;
- f.observer_guid = guid;
+ handleEvent.observer_guid = guid;
+ eventListenerOrFunction.observer_guid = guid;
// Don't add the same handler more than once.
if (!this.handlers[guid]) {
- this.handlers[guid] = func;
+ this.handlers[guid] = handleEvent;
this.numHandlers++;
if (this.numHandlers == 1) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
@@ -783,12 +801,20 @@ Channel.prototype.subscribe = function(f, c) {
/**
* Unsubscribes the function with the given guid from the channel.
*/
-Channel.prototype.unsubscribe = function(f) {
- // need a function to unsubscribe
- forceFunction(f);
+Channel.prototype.unsubscribe = function(eventListenerOrFunction) {
+ checkSubscriptionArgument(eventListenerOrFunction);
+ var handleEvent, guid, handler;
- var guid = f.observer_guid,
- handler = this.handlers[guid];
+ if (eventListenerOrFunction && typeof eventListenerOrFunction === "object") {
+ // Received an EventListener object implementing the handleEvent interface
+ handleEvent = eventListenerOrFunction.handleEvent;
+ } else {
+ // Received a function to handle event
+ handleEvent = eventListenerOrFunction;
+ }
+
+ guid = handleEvent.observer_guid;
+ handler = this.handlers[guid];
if (handler) {
delete this.handlers[guid];
this.numHandlers--;
@@ -895,10 +921,11 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
- ONLINE_EVENT: 2
+ ONLINE_EVENT: 2,
+ EVAL_BRIDGE: 3
},
jsToNativeBridgeMode, // Set lazily.
- nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
+ nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
pollEnabled = false,
bridgeSecret = -1;
@@ -921,6 +948,9 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
+ // If args is not provided, default to an empty array
+ args = args || [];
+
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
if (utils.typeName(args[i]) == 'ArrayBuffer') {
@@ -930,7 +960,6 @@ function androidExec(success, fail, service, action, args) {
var callbackId = service + cordova.callbackId++,
argsJson = JSON.stringify(args);
-
if (success || fail) {
cordova.callbacks[callbackId] = {success:success, fail:fail};
}
@@ -950,6 +979,17 @@ function androidExec(success, fail, service, action, args) {
}
androidExec.init = function() {
+ //CB-11828
+ //This failsafe checks the version of Android and if it's Jellybean, it switches it to
+ //using the Online Event bridge for communicating from Native to JS
+ //
+ //It's ugly, but it's necessary.
+ var check = navigator.userAgent.toLowerCase().match(/android\s[0-9].[0-9]/);
+ var version_code = check && check[0].match(/4.[0-3].*/);
+ if (version_code != null && nativeToJsBridgeMode == nativeToJsModes.EVAL_BRIDGE) {
+ nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT;
+ }
+
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
channel.onNativeReady.fire();
};
@@ -1291,10 +1331,12 @@ define("cordova/init_b", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
+var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform');
+var pluginloader = require('cordova/pluginloader');
var utils = require('cordova/utils');
-var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
+var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady, channel.onPluginsReady];
// setting exec
cordova.exec = require('cordova/exec');
@@ -1379,10 +1421,19 @@ if (window._nativeReady) {
// Call the platform-specific initialization.
platform.bootstrap && platform.bootstrap();
+// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js.
+// The delay allows the attached modules to be defined before the plugin loader looks for them.
+setTimeout(function() {
+ pluginloader.load(function() {
+ channel.onPluginsReady.fire();
+ });
+}, 0);
+
/**
* Create all cordova objects once native side is ready.
*/
channel.join(function() {
+ modulemapper.mapModules(window);
platform.initialize && platform.initialize();
@@ -1501,9 +1552,109 @@ exports.reset();
});
+// file: src/common/modulemapper_b.js
+define("cordova/modulemapper_b", function(require, exports, module) {
+
+var builder = require('cordova/builder'),
+ symbolList = [],
+ deprecationMap;
+
+exports.reset = function() {
+ symbolList = [];
+ deprecationMap = {};
+};
+
+function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) {
+ symbolList.push(strategy, moduleName, symbolPath);
+ if (opt_deprecationMessage) {
+ deprecationMap[symbolPath] = opt_deprecationMessage;
+ }
+}
+
+// Note: Android 2.3 does have Function.bind().
+exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('c', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('m', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) {
+ addEntry('d', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.runs = function(moduleName) {
+ addEntry('r', moduleName, null);
+};
+
+function prepareNamespace(symbolPath, context) {
+ if (!symbolPath) {
+ return context;
+ }
+ var parts = symbolPath.split('.');
+ var cur = context;
+ for (var i = 0, part; part = parts[i]; ++i) {
+ cur = cur[part] = cur[part] || {};
+ }
+ return cur;
+}
+
+exports.mapModules = function(context) {
+ var origSymbols = {};
+ context.CDV_origSymbols = origSymbols;
+ for (var i = 0, len = symbolList.length; i < len; i += 3) {
+ var strategy = symbolList[i];
+ var moduleName = symbolList[i + 1];
+ var module = require(moduleName);
+ // <runs/>
+ if (strategy == 'r') {
+ continue;
+ }
+ var symbolPath = symbolList[i + 2];
+ var lastDot = symbolPath.lastIndexOf('.');
+ var namespace = symbolPath.substr(0, lastDot);
+ var lastName = symbolPath.substr(lastDot + 1);
+
+ var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null;
+ var parentObj = prepareNamespace(namespace, context);
+ var target = parentObj[lastName];
+
+ if (strategy == 'm' && target) {
+ builder.recursiveMerge(target, module);
+ } else if ((strategy == 'd' && !target) || (strategy != 'd')) {
+ if (!(symbolPath in origSymbols)) {
+ origSymbols[symbolPath] = target;
+ }
+ builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg);
+ }
+ }
+};
+
+exports.getOriginalSymbol = function(context, symbolPath) {
+ var origSymbols = context.CDV_origSymbols;
+ if (origSymbols && (symbolPath in origSymbols)) {
+ return origSymbols[symbolPath];
+ }
+ var parts = symbolPath.split('.');
+ var obj = context;
+ for (var i = 0; i < parts.length; ++i) {
+ obj = obj && obj[parts[i]];
+ }
+ return obj;
+};
+
+exports.reset();
+
+
+});
+
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/platform.js
define("cordova/platform", function(require, exports, module) {
+// The last resume event that was received that had the result of a plugin call.
+var lastResumeEvent = null;
+
module.exports = {
id: 'android',
bootstrap: function() {
@@ -1543,6 +1694,19 @@ module.exports = {
bindButtonChannel('volumeup');
bindButtonChannel('volumedown');
+ // The resume event is not "sticky", but it is possible that the event
+ // will contain the result of a plugin call. We need to ensure that the
+ // plugin result is delivered even after the event is fired (CB-10498)
+ var cordovaAddEventListener = document.addEventListener;
+
+ document.addEventListener = function(evt, handler, capture) {
+ cordovaAddEventListener(evt, handler, capture);
+
+ if (evt === 'resume' && lastResumeEvent) {
+ handler(lastResumeEvent);
+ }
+ };
+
// Let native code know we are all done on the JS side.
// Native code will then un-hide the WebView.
channel.onCordovaReady.subscribe(function() {
@@ -1564,12 +1728,30 @@ function onMessageFromNative(msg) {
case 'searchbutton':
// App life cycle events
case 'pause':
- case 'resume':
// Volume events
case 'volumedownbutton':
case 'volumeupbutton':
cordova.fireDocumentEvent(action);
break;
+ case 'resume':
+ if(arguments.length > 1 && msg.pendingResult) {
+ if(arguments.length === 2) {
+ msg.pendingResult.result = arguments[1];
+ } else {
+ // The plugin returned a multipart message
+ var res = [];
+ for(var i = 1; i < arguments.length; i++) {
+ res.push(arguments[i]);
+ }
+ msg.pendingResult.result = res;
+ }
+
+ // Save the plugin result so that it can be delivered to the js
+ // even if they miss the initial firing of the event
+ lastResumeEvent = msg;
+ }
+ cordova.fireDocumentEvent(action, msg);
+ break;
default:
throw new Error('Unknown event action ' + action);
}
@@ -1673,10 +1855,6 @@ module.exports = {
// file: src/common/pluginloader.js
define("cordova/pluginloader", function(require, exports, module) {
-/*
- NOTE: this file is NOT used when we use the browserify workflow
-*/
-
var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil');
@@ -1786,6 +1964,54 @@ exports.load = function(callback) {
});
+// file: src/common/pluginloader_b.js
+define("cordova/pluginloader_b", function(require, exports, module) {
+
+var modulemapper = require('cordova/modulemapper');
+
+// Handler for the cordova_plugins.js content.
+// See plugman's plugin_loader.js for the details of this object.
+function handlePluginsObject(moduleList) {
+ // if moduleList is not defined or empty, we've nothing to do
+ if (!moduleList || !moduleList.length) {
+ return;
+ }
+
+ // Loop through all the modules and then through their clobbers and merges.
+ for (var i = 0, module; module = moduleList[i]; i++) {
+ if (module.clobbers && module.clobbers.length) {
+ for (var j = 0; j < module.clobbers.length; j++) {
+ modulemapper.clobbers(module.id, module.clobbers[j]);
+ }
+ }
+
+ if (module.merges && module.merges.length) {
+ for (var k = 0; k < module.merges.length; k++) {
+ modulemapper.merges(module.id, module.merges[k]);
+ }
+ }
+
+ // Finally, if runs is truthy we want to simply require() the module.
+ if (module.runs) {
+ modulemapper.runs(module.id);
+ }
+ }
+}
+
+// Loads all plugins' js-modules. Plugin loading is syncronous in browserified bundle
+// but the method accepts callback to be compatible with non-browserify flow.
+// onDeviceReady is blocked on onPluginsReady. onPluginsReady is fired when there are
+// no plugins to load, or they are all done.
+exports.load = function(callback) {
+ var moduleList = require("cordova/plugin_list");
+ handlePluginsObject(moduleList);
+
+ callback();
+};
+
+
+});
+
// file: src/common/urlutil.js
define("cordova/urlutil", function(require, exports, module) {
@@ -1895,7 +2121,10 @@ utils.clone = function(obj) {
retVal = {};
for(i in obj){
- if(!(i in retVal) || retVal[i] != obj[i]) {
+ // https://issues.apache.org/jira/browse/CB-11522 'unknown' type may be returned in
+ // custom protocol activation case on Windows Phone 8.1 causing "No such interface supported" exception
+ // on cloning.
+ if((!(i in retVal) || retVal[i] != obj[i]) && typeof obj[i] != 'undefined' && typeof obj[i] != 'unknown') {
retVal[i] = utils.clone(obj[i]);
}
}
diff --git a/StoneIsland/platforms/android/platform_www/cordova_plugins.js b/StoneIsland/platforms/android/platform_www/cordova_plugins.js
index a2734881..722b1683 100755
--- a/StoneIsland/platforms/android/platform_www/cordova_plugins.js
+++ b/StoneIsland/platforms/android/platform_www/cordova_plugins.js
@@ -104,6 +104,14 @@ module.exports = [
"cordova.plugins.Keyboard"
],
"runs": true
+ },
+ {
+ "id": "phonegap-plugin-push.PushNotification",
+ "file": "plugins/phonegap-plugin-push/www/push.js",
+ "pluginId": "phonegap-plugin-push",
+ "clobbers": [
+ "PushNotification"
+ ]
}
];
module.exports.metadata =
@@ -121,7 +129,8 @@ module.exports.metadata =
"cordova-plugin-splashscreen": "4.0.0",
"cordova-plugin-compat": "1.1.0",
"cordova-plugin-geolocation": "2.4.0",
- "ionic-plugin-keyboard": "2.2.1"
-}
+ "ionic-plugin-keyboard": "2.2.1",
+ "phonegap-plugin-push": "1.9.2"
+};
// BOTTOM OF METADATA
}); \ No newline at end of file
diff --git a/StoneIsland/platforms/android/platform_www/plugins/phonegap-plugin-push/www/push.js b/StoneIsland/platforms/android/platform_www/plugins/phonegap-plugin-push/www/push.js
new file mode 100644
index 00000000..a5315486
--- /dev/null
+++ b/StoneIsland/platforms/android/platform_www/plugins/phonegap-plugin-push/www/push.js
@@ -0,0 +1,329 @@
+cordova.define("phonegap-plugin-push.PushNotification", function(require, exports, module) {
+/* global cordova:false */
+/* globals window */
+
+/*!
+ * Module dependencies.
+ */
+
+var exec = cordova.require('cordova/exec');
+
+/**
+ * PushNotification constructor.
+ *
+ * @param {Object} options to initiate Push Notifications.
+ * @return {PushNotification} instance that can be monitored and cancelled.
+ */
+
+var PushNotification = function(options) {
+ this._handlers = {
+ 'registration': [],
+ 'notification': [],
+ 'error': []
+ };
+
+ // require options parameter
+ if (typeof options === 'undefined') {
+ throw new Error('The options argument is required.');
+ }
+
+ // store the options to this object instance
+ this.options = options;
+
+ // triggered on registration and notification
+ var that = this;
+ var success = function(result) {
+ if (result && typeof result.registrationId !== 'undefined') {
+ that.emit('registration', result);
+ } else if (result && result.additionalData && typeof result.additionalData.actionCallback !== 'undefined') {
+ var executeFuctionOrEmitEventByName = function(callbackName, context, arg) {
+ var namespaces = callbackName.split('.');
+ var func = namespaces.pop();
+ for (var i = 0; i < namespaces.length; i++) {
+ context = context[namespaces[i]];
+ }
+
+ if (typeof context[func] === 'function') {
+ context[func].call(context, arg);
+ } else {
+ that.emit(callbackName, arg);
+ }
+ };
+
+ executeFuctionOrEmitEventByName(result.additionalData.actionCallback, window, result);
+ } else if (result) {
+ that.emit('notification', result);
+ }
+ };
+
+ // triggered on error
+ var fail = function(msg) {
+ var e = (typeof msg === 'string') ? new Error(msg) : msg;
+ that.emit('error', e);
+ };
+
+ // wait at least one process tick to allow event subscriptions
+ setTimeout(function() {
+ exec(success, fail, 'PushNotification', 'init', [options]);
+ }, 10);
+};
+
+/**
+ * Unregister from push notifications
+ */
+
+PushNotification.prototype.unregister = function(successCallback, errorCallback, options) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unregister failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unregister failure: success callback parameter must be a function');
+ return;
+ }
+
+ var that = this;
+ var cleanHandlersAndPassThrough = function() {
+ if (!options) {
+ that._handlers = {
+ 'registration': [],
+ 'notification': [],
+ 'error': []
+ };
+ }
+ successCallback();
+ };
+
+ exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]);
+};
+
+/**
+ * subscribe to a topic
+ * @param {String} topic topic to subscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.subscribe = function(topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]);
+};
+
+/**
+ * unsubscribe to a topic
+ * @param {String} topic topic to unsubscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.unsubscribe = function(topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]);
+};
+
+/**
+ * Call this to set the application icon badge
+ */
+
+PushNotification.prototype.setApplicationIconBadgeNumber = function(successCallback, errorCallback, badge) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [{badge: badge}]);
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.getApplicationIconBadgeNumber = function(successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []);
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.clearAllNotifications = function(successCallback, errorCallback) {
+ if (!successCallback) { successCallback = function() {}; }
+ if (!errorCallback) { errorCallback = function() {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []);
+};
+
+/**
+ * Listen for an event.
+ *
+ * Any event is supported, but the following are built-in:
+ *
+ * - registration
+ * - notification
+ * - error
+ *
+ * @param {String} eventName to subscribe to.
+ * @param {Function} callback triggered on the event.
+ */
+
+PushNotification.prototype.on = function(eventName, callback) {
+ if (!this._handlers.hasOwnProperty(eventName)) {
+ this._handlers[eventName] = [];
+ }
+ this._handlers[eventName].push(callback);
+};
+
+/**
+ * Remove event listener.
+ *
+ * @param {String} eventName to match subscription.
+ * @param {Function} handle function associated with event.
+ */
+
+PushNotification.prototype.off = function (eventName, handle) {
+ if (this._handlers.hasOwnProperty(eventName)) {
+ var handleIndex = this._handlers[eventName].indexOf(handle);
+ if (handleIndex >= 0) {
+ this._handlers[eventName].splice(handleIndex, 1);
+ }
+ }
+};
+
+/**
+ * Emit an event.
+ *
+ * This is intended for internal use only.
+ *
+ * @param {String} eventName is the event to trigger.
+ * @param {*} all arguments are passed to the event listeners.
+ *
+ * @return {Boolean} is true when the event is triggered otherwise false.
+ */
+
+PushNotification.prototype.emit = function() {
+ var args = Array.prototype.slice.call(arguments);
+ var eventName = args.shift();
+
+ if (!this._handlers.hasOwnProperty(eventName)) {
+ return false;
+ }
+
+ for (var i = 0, length = this._handlers[eventName].length; i < length; i++) {
+ var callback = this._handlers[eventName][i];
+ if (typeof callback === 'function') {
+ callback.apply(undefined,args);
+ } else {
+ console.log('event handler: ' + eventName + ' must be a function');
+ }
+ }
+
+ return true;
+};
+
+PushNotification.prototype.finish = function(successCallback, errorCallback, id) {
+ if (!successCallback) { successCallback = function() {}; }
+ if (!errorCallback) { errorCallback = function() {}; }
+ if (!id) { id = 'handler'; }
+
+ if (typeof successCallback !== 'function') {
+ console.log('finish failure: success callback parameter must be a function');
+ return;
+ }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('finish failure: failure parameter not a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]);
+};
+
+/*!
+ * Push Notification Plugin.
+ */
+
+module.exports = {
+ /**
+ * Register for Push Notifications.
+ *
+ * This method will instantiate a new copy of the PushNotification object
+ * and start the registration process.
+ *
+ * @param {Object} options
+ * @return {PushNotification} instance
+ */
+
+ init: function(options) {
+ return new PushNotification(options);
+ },
+
+ hasPermission: function(successCallback, errorCallback) {
+ exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []);
+ },
+
+ /**
+ * PushNotification Object.
+ *
+ * Expose the PushNotification object for direct use
+ * and testing. Typically, you should use the
+ * .init helper method.
+ */
+
+ PushNotification: PushNotification
+};
+
+});
diff --git a/StoneIsland/platforms/android/project.properties b/StoneIsland/platforms/android/project.properties
index b06f0fa0..6936daa2 100755
--- a/StoneIsland/platforms/android/project.properties
+++ b/StoneIsland/platforms/android/project.properties
@@ -10,5 +10,9 @@
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-22
+target=android-25
android.library.reference.1=CordovaLib
+cordova.gradle.include.1=phonegap-plugin-push/stoneisland-push.gradle
+cordova.system.library.1=com.android.support:support-v13:23+
+cordova.system.library.2=com.google.android.gms:play-services-gcm:9.8+
+cordova.system.library.3=me.leolin:ShortcutBadger:1.1.11@aar \ No newline at end of file
diff --git a/StoneIsland/platforms/android/res/values/strings.xml b/StoneIsland/platforms/android/res/values/strings.xml
index bd922fe9..60540e70 100755
--- a/StoneIsland/platforms/android/res/values/strings.xml
+++ b/StoneIsland/platforms/android/res/values/strings.xml
@@ -3,4 +3,5 @@
<string name="app_name">Stone Island</string>
<string name="launcher_name">@string/app_name</string>
<string name="activity_name">@string/launcher_name</string>
+ <string name="google_app_id">XXXXXXX</string>
</resources>
diff --git a/StoneIsland/platforms/android/res/xml/config.xml b/StoneIsland/platforms/android/res/xml/config.xml
index b9330793..00961deb 100755..100644
--- a/StoneIsland/platforms/android/res/xml/config.xml
+++ b/StoneIsland/platforms/android/res/xml/config.xml
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
-<widget id="us.okfoc.stoneisland" version="0.7.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+<widget id="us.okfoc.stoneisland" version="0.8.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<feature name="ParsePlugin">
<param name="android-package" value="org.apache.cordova.core.ParsePlugin" />
</feature>
@@ -69,4 +69,7 @@
<preference name="AllowInlineMediaPlayback" value="true" />
<preference name="AndroidLaunchMode" value="singleTop" />
<preference name="android-minSdkVersion" value="21" />
+ <feature name="PushNotification">
+ <param name="android-package" value="com.adobe.phonegap.push.PushPlugin" />
+ </feature>
</widget>
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/BackgroundActionButtonHandler.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/BackgroundActionButtonHandler.java
new file mode 100644
index 00000000..3ccea6cb
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/BackgroundActionButtonHandler.java
@@ -0,0 +1,41 @@
+package com.adobe.phonegap.push;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.support.v4.app.RemoteInput;
+
+public class BackgroundActionButtonHandler extends BroadcastReceiver implements PushConstants {
+ private static String LOG_TAG = "PushPlugin_BackgroundActionButtonHandler";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ Log.d(LOG_TAG, "BackgroundActionButtonHandler = " + extras);
+
+ int notId = intent.getIntExtra(NOT_ID, 0);
+ Log.d(LOG_TAG, "not id = " + notId);
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(GCMIntentService.getAppName(context), notId);
+
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+
+ originalExtras.putBoolean(FOREGROUND, false);
+ originalExtras.putBoolean(COLDSTART, false);
+ originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK));
+
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString();
+ Log.d(LOG_TAG, "response: " + inputString);
+ originalExtras.putString(INLINE_REPLY, inputString);
+ }
+
+ PushPlugin.sendExtras(originalExtras);
+ }
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java
new file mode 100644
index 00000000..e1a2b75c
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java
@@ -0,0 +1,802 @@
+package com.adobe.phonegap.push;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+import android.support.v4.app.RemoteInput;
+import android.text.Html;
+import android.text.Spanned;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmListenerService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Random;
+
+@SuppressLint("NewApi")
+public class GCMIntentService extends GcmListenerService implements PushConstants {
+
+ private static final String LOG_TAG = "PushPlugin_GCMIntentService";
+ private static HashMap<Integer, ArrayList<String>> messageMap = new HashMap<Integer, ArrayList<String>>();
+
+ public void setNotification(int notId, String message){
+ ArrayList<String> messageList = messageMap.get(notId);
+ if(messageList == null) {
+ messageList = new ArrayList<String>();
+ messageMap.put(notId, messageList);
+ }
+
+ if(message.isEmpty()){
+ messageList.clear();
+ }else{
+ messageList.add(message);
+ }
+ }
+
+ @Override
+ public void onMessageReceived(String from, Bundle extras) {
+ Log.d(LOG_TAG, "onMessage - from: " + from);
+
+ if (extras != null) {
+ Context applicationContext = getApplicationContext();
+
+ SharedPreferences prefs = applicationContext.getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ boolean forceShow = prefs.getBoolean(FORCE_SHOW, false);
+ boolean clearBadge = prefs.getBoolean(CLEAR_BADGE, false);
+
+ extras = normalizeExtras(applicationContext, extras);
+
+ if (clearBadge) {
+ PushPlugin.setApplicationIconBadgeNumber(getApplicationContext(), 0);
+ }
+
+ // if we are in the foreground and forceShow is `false` only send data
+ if (!forceShow && PushPlugin.isInForeground()) {
+ Log.d(LOG_TAG, "foreground");
+ extras.putBoolean(FOREGROUND, true);
+ extras.putBoolean(COLDSTART, false);
+ PushPlugin.sendExtras(extras);
+ }
+ // if we are in the foreground and forceShow is `true`, force show the notification if the data has at least a message or title
+ else if (forceShow && PushPlugin.isInForeground()) {
+ Log.d(LOG_TAG, "foreground force");
+ extras.putBoolean(FOREGROUND, true);
+ extras.putBoolean(COLDSTART, false);
+
+ showNotificationIfPossible(applicationContext, extras);
+ }
+ // if we are not in the foreground always send notification if the data has at least a message or title
+ else {
+ Log.d(LOG_TAG, "background");
+ extras.putBoolean(FOREGROUND, false);
+ extras.putBoolean(COLDSTART, PushPlugin.isActive());
+
+ showNotificationIfPossible(applicationContext, extras);
+ }
+ }
+ }
+
+ /*
+ * Change a values key in the extras bundle
+ */
+ private void replaceKey(Context context, String oldKey, String newKey, Bundle extras, Bundle newExtras) {
+ Object value = extras.get(oldKey);
+ if ( value != null ) {
+ if (value instanceof String) {
+ value = localizeKey(context, newKey, (String) value);
+
+ newExtras.putString(newKey, (String) value);
+ } else if (value instanceof Boolean) {
+ newExtras.putBoolean(newKey, (Boolean) value);
+ } else if (value instanceof Number) {
+ newExtras.putDouble(newKey, ((Number) value).doubleValue());
+ } else {
+ newExtras.putString(newKey, String.valueOf(value));
+ }
+ }
+ }
+
+ /*
+ * Normalize localization for key
+ */
+ private String localizeKey(Context context, String key, String value) {
+ if (key.equals(TITLE) || key.equals(MESSAGE) || key.equals(SUMMARY_TEXT)) {
+ try {
+ JSONObject localeObject = new JSONObject(value);
+
+ String localeKey = localeObject.getString(LOC_KEY);
+
+ ArrayList<String> localeFormatData = new ArrayList<String>();
+ if (!localeObject.isNull(LOC_DATA)) {
+ String localeData = localeObject.getString(LOC_DATA);
+ JSONArray localeDataArray = new JSONArray(localeData);
+ for (int i = 0 ; i < localeDataArray.length(); i++) {
+ localeFormatData.add(localeDataArray.getString(i));
+ }
+ }
+
+ String packageName = context.getPackageName();
+ Resources resources = context.getResources();
+
+ int resourceId = resources.getIdentifier(localeKey, "string", packageName);
+
+ if (resourceId != 0) {
+ return resources.getString(resourceId, localeFormatData.toArray());
+ }
+ else {
+ Log.d(LOG_TAG, "can't find resource for locale key = " + localeKey);
+
+ return value;
+ }
+ }
+ catch(JSONException e) {
+ Log.d(LOG_TAG, "no locale found for key = " + key + ", error " + e.getMessage());
+
+ return value;
+ }
+ }
+
+ return value;
+ }
+
+ /*
+ * Replace alternate keys with our canonical value
+ */
+ private String normalizeKey(String key) {
+ if (key.equals(BODY) || key.equals(ALERT) || key.equals(GCM_NOTIFICATION_BODY) || key.equals(TWILIO_BODY)) {
+ return MESSAGE;
+ } else if (key.equals(TWILIO_TITLE)) {
+ return TITLE;
+ }else if (key.equals(MSGCNT) || key.equals(BADGE)) {
+ return COUNT;
+ } else if (key.equals(SOUNDNAME) || key.equals(TWILIO_SOUND)) {
+ return SOUND;
+ } else if (key.startsWith(GCM_NOTIFICATION)) {
+ return key.substring(GCM_NOTIFICATION.length()+1, key.length());
+ } else if (key.startsWith(GCM_N)) {
+ return key.substring(GCM_N.length()+1, key.length());
+ } else if (key.startsWith(UA_PREFIX)) {
+ key = key.substring(UA_PREFIX.length()+1, key.length());
+ return key.toLowerCase();
+ } else {
+ return key;
+ }
+ }
+
+ /*
+ * Parse bundle into normalized keys.
+ */
+ private Bundle normalizeExtras(Context context, Bundle extras) {
+ Log.d(LOG_TAG, "normalize extras");
+ Iterator<String> it = extras.keySet().iterator();
+ Bundle newExtras = new Bundle();
+
+ while (it.hasNext()) {
+ String key = it.next();
+
+ Log.d(LOG_TAG, "key = " + key);
+
+ // If normalizeKeythe key is "data" or "message" and the value is a json object extract
+ // This is to support parse.com and other services. Issue #147 and pull #218
+ if (key.equals(PARSE_COM_DATA) || key.equals(MESSAGE)) {
+ Object json = extras.get(key);
+ // Make sure data is json object stringified
+ if ( json instanceof String && ((String) json).startsWith("{") ) {
+ Log.d(LOG_TAG, "extracting nested message data from key = " + key);
+ try {
+ // If object contains message keys promote each value to the root of the bundle
+ JSONObject data = new JSONObject((String) json);
+ if ( data.has(ALERT) || data.has(MESSAGE) || data.has(BODY) || data.has(TITLE) ) {
+ Iterator<String> jsonIter = data.keys();
+ while (jsonIter.hasNext()) {
+ String jsonKey = jsonIter.next();
+
+ Log.d(LOG_TAG, "key = data/" + jsonKey);
+
+ String value = data.getString(jsonKey);
+ jsonKey = normalizeKey(jsonKey);
+ value = localizeKey(context, jsonKey, value);
+
+ newExtras.putString(jsonKey, value);
+ }
+ }
+ } catch( JSONException e) {
+ Log.e(LOG_TAG, "normalizeExtras: JSON exception");
+ }
+ }
+ } else if (key.equals(("notification"))) {
+ Bundle value = extras.getBundle(key);
+ Iterator<String> iterator = value.keySet().iterator();
+ while (iterator.hasNext()) {
+ String notifkey = iterator.next();
+
+ Log.d(LOG_TAG, "notifkey = " + notifkey);
+ String newKey = normalizeKey(notifkey);
+ Log.d(LOG_TAG, "replace key " + notifkey + " with " + newKey);
+
+ String valueData = value.getString(notifkey);
+ valueData = localizeKey(context, newKey, valueData);
+
+ newExtras.putString(newKey, valueData);
+ }
+ continue;
+ }
+
+ String newKey = normalizeKey(key);
+ Log.d(LOG_TAG, "replace key " + key + " with " + newKey);
+ replaceKey(context, key, newKey, extras, newExtras);
+
+ } // while
+
+ return newExtras;
+ }
+
+ private int extractBadgeCount(Bundle extras) {
+ int count = -1;
+ String msgcnt = extras.getString(COUNT);
+
+ try {
+ if (msgcnt != null) {
+ count = Integer.parseInt(msgcnt);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, e.getLocalizedMessage(), e);
+ }
+
+ return count;
+ }
+
+ private void showNotificationIfPossible (Context context, Bundle extras) {
+
+ // Send a notification if there is a message or title, otherwise just send data
+ String message = extras.getString(MESSAGE);
+ String title = extras.getString(TITLE);
+ String contentAvailable = extras.getString(CONTENT_AVAILABLE);
+ String forceStart = extras.getString(FORCE_START);
+ int badgeCount = extractBadgeCount(extras);
+ if (badgeCount >= 0) {
+ Log.d(LOG_TAG, "count =[" + badgeCount + "]");
+ PushPlugin.setApplicationIconBadgeNumber(context, badgeCount);
+ }
+
+ Log.d(LOG_TAG, "message =[" + message + "]");
+ Log.d(LOG_TAG, "title =[" + title + "]");
+ Log.d(LOG_TAG, "contentAvailable =[" + contentAvailable + "]");
+ Log.d(LOG_TAG, "forceStart =[" + forceStart + "]");
+
+ if ((message != null && message.length() != 0) ||
+ (title != null && title.length() != 0)) {
+
+ Log.d(LOG_TAG, "create notification");
+
+ if(title == null || title.isEmpty()){
+ extras.putString(TITLE, getAppName(this));
+ }
+
+ createNotification(context, extras);
+ }
+
+ if(!PushPlugin.isActive() && "1".equals(forceStart)){
+ Log.d(LOG_TAG, "app is not running but we should start it and put in background");
+ Intent intent = new Intent(this, PushHandlerActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(PUSH_BUNDLE, extras);
+ intent.putExtra(START_IN_BACKGROUND, true);
+ intent.putExtra(FOREGROUND, false);
+ startActivity(intent);
+ } else if ("1".equals(contentAvailable)) {
+ Log.d(LOG_TAG, "app is not running and content available true");
+ Log.d(LOG_TAG, "send notification event");
+ PushPlugin.sendExtras(extras);
+ }
+ }
+
+ public void createNotification(Context context, Bundle extras) {
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ String appName = getAppName(this);
+ String packageName = context.getPackageName();
+ Resources resources = context.getResources();
+
+ int notId = parseInt(NOT_ID, extras);
+ Intent notificationIntent = new Intent(this, PushHandlerActivity.class);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notificationIntent.putExtra(PUSH_BUNDLE, extras);
+ notificationIntent.putExtra(NOT_ID, notId);
+
+ int requestCode = new Random().nextInt();
+ PendingIntent contentIntent = PendingIntent.getActivity(this, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder mBuilder =
+ new NotificationCompat.Builder(context)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle(fromHtml(extras.getString(TITLE)))
+ .setTicker(fromHtml(extras.getString(TITLE)))
+ .setContentIntent(contentIntent)
+ .setAutoCancel(true);
+
+ SharedPreferences prefs = context.getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ String localIcon = prefs.getString(ICON, null);
+ String localIconColor = prefs.getString(ICON_COLOR, null);
+ boolean soundOption = prefs.getBoolean(SOUND, true);
+ boolean vibrateOption = prefs.getBoolean(VIBRATE, true);
+ Log.d(LOG_TAG, "stored icon=" + localIcon);
+ Log.d(LOG_TAG, "stored iconColor=" + localIconColor);
+ Log.d(LOG_TAG, "stored sound=" + soundOption);
+ Log.d(LOG_TAG, "stored vibrate=" + vibrateOption);
+
+ /*
+ * Notification Vibration
+ */
+
+ setNotificationVibration(extras, vibrateOption, mBuilder);
+
+ /*
+ * Notification Icon Color
+ *
+ * Sets the small-icon background color of the notification.
+ * To use, add the `iconColor` key to plugin android options
+ *
+ */
+ setNotificationIconColor(extras.getString("color"), mBuilder, localIconColor);
+
+ /*
+ * Notification Icon
+ *
+ * Sets the small-icon of the notification.
+ *
+ * - checks the plugin options for `icon` key
+ * - if none, uses the application icon
+ *
+ * The icon value must be a string that maps to a drawable resource.
+ * If no resource is found, falls
+ *
+ */
+ setNotificationSmallIcon(context, extras, packageName, resources, mBuilder, localIcon);
+
+ /*
+ * Notification Large-Icon
+ *
+ * Sets the large-icon of the notification
+ *
+ * - checks the gcm data for the `image` key
+ * - checks to see if remote image, loads it.
+ * - checks to see if assets image, Loads It.
+ * - checks to see if resource image, LOADS IT!
+ * - if none, we don't set the large icon
+ *
+ */
+ setNotificationLargeIcon(extras, packageName, resources, mBuilder);
+
+ /*
+ * Notification Sound
+ */
+ if (soundOption) {
+ setNotificationSound(context, extras, mBuilder);
+ }
+
+ /*
+ * LED Notification
+ */
+ setNotificationLedColor(extras, mBuilder);
+
+ /*
+ * Priority Notification
+ */
+ setNotificationPriority(extras, mBuilder);
+
+ /*
+ * Notification message
+ */
+ setNotificationMessage(notId, extras, mBuilder);
+
+ /*
+ * Notification count
+ */
+ setNotificationCount(context, extras, mBuilder);
+
+ /*
+ * Notification count
+ */
+ setVisibility(context, extras, mBuilder);
+
+ /*
+ * Notification add actions
+ */
+ createActions(extras, mBuilder, resources, packageName, notId);
+
+ mNotificationManager.notify(appName, notId, mBuilder.build());
+ }
+
+ private void updateIntent(Intent intent, String callback, Bundle extras, boolean foreground, int notId) {
+ intent.putExtra(CALLBACK, callback);
+ intent.putExtra(PUSH_BUNDLE, extras);
+ intent.putExtra(FOREGROUND, foreground);
+ intent.putExtra(NOT_ID, notId);
+ }
+
+ private void createActions(Bundle extras, NotificationCompat.Builder mBuilder, Resources resources, String packageName, int notId) {
+ Log.d(LOG_TAG, "create actions: with in-line");
+ String actions = extras.getString(ACTIONS);
+ if (actions != null) {
+ try {
+ JSONArray actionsArray = new JSONArray(actions);
+ ArrayList<NotificationCompat.Action> wActions = new ArrayList<NotificationCompat.Action>();
+ for (int i=0; i < actionsArray.length(); i++) {
+ int min = 1;
+ int max = 2000000000;
+ Random random = new Random();
+ int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min;
+ Log.d(LOG_TAG, "adding action");
+ JSONObject action = actionsArray.getJSONObject(i);
+ Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
+ boolean foreground = action.optBoolean(FOREGROUND, true);
+ boolean inline = action.optBoolean("inline", false);
+ Intent intent = null;
+ PendingIntent pIntent = null;
+ if (inline) {
+ Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = " + android.os.Build.VERSION_CODES.M);
+ if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
+ Log.d(LOG_TAG, "push activity");
+ intent = new Intent(this, PushHandlerActivity.class);
+ } else {
+ Log.d(LOG_TAG, "push receiver");
+ intent = new Intent(this, BackgroundActionButtonHandler.class);
+ }
+
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+
+ if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
+ Log.d(LOG_TAG, "push activity for notId " + notId);
+ pIntent = PendingIntent.getActivity(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT);
+ } else {
+ Log.d(LOG_TAG, "push receiver for notId " + notId);
+ pIntent = PendingIntent.getBroadcast(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT);
+ }
+ } else if (foreground) {
+ intent = new Intent(this, PushHandlerActivity.class);
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+ pIntent = PendingIntent.getActivity(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ } else {
+ intent = new Intent(this, BackgroundActionButtonHandler.class);
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+ pIntent = PendingIntent.getBroadcast(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ NotificationCompat.Action.Builder actionBuilder =
+ new NotificationCompat.Action.Builder(resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
+ action.getString(TITLE), pIntent);
+
+ RemoteInput remoteInput = null;
+ if (inline) {
+ Log.d(LOG_TAG, "create remote input");
+ String replyLabel = "Enter your reply here";
+ remoteInput =
+ new RemoteInput.Builder(INLINE_REPLY)
+ .setLabel(replyLabel)
+ .build();
+ actionBuilder.addRemoteInput(remoteInput);
+ }
+
+ NotificationCompat.Action wAction = actionBuilder.build();
+ wActions.add(actionBuilder.build());
+
+ if (inline) {
+ mBuilder.addAction(wAction);
+ } else {
+ mBuilder.addAction(resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
+ action.getString(TITLE), pIntent);
+ }
+ wAction = null;
+ pIntent = null;
+ }
+ mBuilder.extend(new WearableExtender().addActions(wActions));
+ wActions.clear();
+ } catch(JSONException e) {
+ // nope
+ }
+ }
+ }
+
+ private void setNotificationCount(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ int count = extractBadgeCount(extras);
+ if (count >= 0) {
+ Log.d(LOG_TAG, "count =[" + count + "]");
+ mBuilder.setNumber(count);
+ }
+ }
+
+
+ private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String visibilityStr = extras.getString(VISIBILITY);
+ if (visibilityStr != null) {
+ try {
+ Integer visibility = Integer.parseInt(visibilityStr);
+ if (visibility >= NotificationCompat.VISIBILITY_SECRET && visibility <= NotificationCompat.VISIBILITY_PUBLIC) {
+ mBuilder.setVisibility(visibility);
+ } else {
+ Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1");
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void setNotificationVibration(Bundle extras, Boolean vibrateOption, NotificationCompat.Builder mBuilder) {
+ String vibrationPattern = extras.getString(VIBRATION_PATTERN);
+ if (vibrationPattern != null) {
+ String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
+ long[] results = new long[items.length];
+ for (int i = 0; i < items.length; i++) {
+ try {
+ results[i] = Long.parseLong(items[i].trim());
+ } catch (NumberFormatException nfe) {}
+ }
+ mBuilder.setVibrate(results);
+ } else {
+ if (vibrateOption) {
+ mBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
+ }
+ }
+ }
+
+ private void setNotificationMessage(int notId, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String message = extras.getString(MESSAGE);
+
+ String style = extras.getString(STYLE, STYLE_TEXT);
+ if(STYLE_INBOX.equals(style)) {
+ setNotification(notId, message);
+
+ mBuilder.setContentText(fromHtml(message));
+
+ ArrayList<String> messageList = messageMap.get(notId);
+ Integer sizeList = messageList.size();
+ if (sizeList > 1) {
+ String sizeListMessage = sizeList.toString();
+ String stacking = sizeList + " more";
+ if (extras.getString(SUMMARY_TEXT) != null) {
+ stacking = extras.getString(SUMMARY_TEXT);
+ stacking = stacking.replace("%n%", sizeListMessage);
+ }
+ NotificationCompat.InboxStyle notificationInbox = new NotificationCompat.InboxStyle()
+ .setBigContentTitle(fromHtml(extras.getString(TITLE)))
+ .setSummaryText(fromHtml(stacking));
+
+ for (int i = messageList.size() - 1; i >= 0; i--) {
+ notificationInbox.addLine(fromHtml(messageList.get(i)));
+ }
+
+ mBuilder.setStyle(notificationInbox);
+ } else {
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+ if (message != null) {
+ bigText.bigText(fromHtml(message));
+ bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
+ mBuilder.setStyle(bigText);
+ }
+ }
+ } else if (STYLE_PICTURE.equals(style)) {
+ setNotification(notId, "");
+
+ NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
+ bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE)));
+ bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
+ bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
+
+ mBuilder.setContentTitle(fromHtml(extras.getString(TITLE)));
+ mBuilder.setContentText(fromHtml(message));
+
+ mBuilder.setStyle(bigPicture);
+ } else {
+ setNotification(notId, "");
+
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+
+ if (message != null) {
+ mBuilder.setContentText(fromHtml(message));
+
+ bigText.bigText(fromHtml(message));
+ bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
+
+ String summaryText = extras.getString(SUMMARY_TEXT);
+ if (summaryText != null) {
+ bigText.setSummaryText(fromHtml(summaryText));
+ }
+
+ mBuilder.setStyle(bigText);
+ }
+ /*
+ else {
+ mBuilder.setContentText("<missing message content>");
+ }
+ */
+ }
+ }
+
+ private void setNotificationSound(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String soundname = extras.getString(SOUNDNAME);
+ if (soundname == null) {
+ soundname = extras.getString(SOUND);
+ }
+ if (SOUND_RINGTONE.equals(soundname)) {
+ mBuilder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI);
+ } else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
+ Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ + "://" + context.getPackageName() + "/raw/" + soundname);
+ Log.d(LOG_TAG, sound.toString());
+ mBuilder.setSound(sound);
+ } else {
+ mBuilder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI);
+ }
+ }
+
+ private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder mBuilder) {
+ String ledColor = extras.getString(LED_COLOR);
+ if (ledColor != null) {
+ // Converts parse Int Array from ledColor
+ String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
+ int[] results = new int[items.length];
+ for (int i = 0; i < items.length; i++) {
+ try {
+ results[i] = Integer.parseInt(items[i].trim());
+ } catch (NumberFormatException nfe) {}
+ }
+ if (results.length == 4) {
+ mBuilder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500);
+ } else {
+ Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
+ }
+ }
+ }
+
+ private void setNotificationPriority(Bundle extras, NotificationCompat.Builder mBuilder) {
+ String priorityStr = extras.getString(PRIORITY);
+ if (priorityStr != null) {
+ try {
+ Integer priority = Integer.parseInt(priorityStr);
+ if (priority >= NotificationCompat.PRIORITY_MIN && priority <= NotificationCompat.PRIORITY_MAX) {
+ mBuilder.setPriority(priority);
+ } else {
+ Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void setNotificationLargeIcon(Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder) {
+ String gcmLargeIcon = extras.getString(IMAGE); // from gcm
+ if (gcmLargeIcon != null && !"".equals(gcmLargeIcon)) {
+ if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
+ mBuilder.setLargeIcon(getBitmapFromURL(gcmLargeIcon));
+ Log.d(LOG_TAG, "using remote large-icon from gcm");
+ } else {
+ AssetManager assetManager = getAssets();
+ InputStream istr;
+ try {
+ istr = assetManager.open(gcmLargeIcon);
+ Bitmap bitmap = BitmapFactory.decodeStream(istr);
+ mBuilder.setLargeIcon(bitmap);
+ Log.d(LOG_TAG, "using assets large-icon from gcm");
+ } catch (IOException e) {
+ int largeIconId = 0;
+ largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
+ if (largeIconId != 0) {
+ Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
+ mBuilder.setLargeIcon(largeIconBitmap);
+ Log.d(LOG_TAG, "using resources large-icon from gcm");
+ } else {
+ Log.d(LOG_TAG, "Not setting large icon");
+ }
+ }
+ }
+ }
+ }
+
+ private void setNotificationSmallIcon(Context context, Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder, String localIcon) {
+ int iconId = 0;
+ String icon = extras.getString(ICON);
+ if (icon != null && !"".equals(icon)) {
+ iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
+ Log.d(LOG_TAG, "using icon from plugin options");
+ }
+ else if (localIcon != null && !"".equals(localIcon)) {
+ iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName);
+ Log.d(LOG_TAG, "using icon from plugin options");
+ }
+ if (iconId == 0) {
+ Log.d(LOG_TAG, "no icon resource found - using application icon");
+ iconId = context.getApplicationInfo().icon;
+ }
+ mBuilder.setSmallIcon(iconId);
+ }
+
+ private void setNotificationIconColor(String color, NotificationCompat.Builder mBuilder, String localIconColor) {
+ int iconColor = 0;
+ if (color != null && !"".equals(color)) {
+ try {
+ iconColor = Color.parseColor(color);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "couldn't parse color from android options");
+ }
+ }
+ else if (localIconColor != null && !"".equals(localIconColor)) {
+ try {
+ iconColor = Color.parseColor(localIconColor);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "couldn't parse color from android options");
+ }
+ }
+ if (iconColor != 0) {
+ mBuilder.setColor(iconColor);
+ }
+ }
+
+ public Bitmap getBitmapFromURL(String strURL) {
+ try {
+ URL url = new URL(strURL);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setDoInput(true);
+ connection.connect();
+ InputStream input = connection.getInputStream();
+ return BitmapFactory.decodeStream(input);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String getAppName(Context context) {
+ CharSequence appName = context.getPackageManager().getApplicationLabel(context.getApplicationInfo());
+ return (String)appName;
+ }
+
+ private int parseInt(String value, Bundle extras) {
+ int retval = 0;
+
+ try {
+ retval = Integer.parseInt(extras.getString(value));
+ }
+ catch(NumberFormatException e) {
+ Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
+ }
+ catch(Exception e) {
+ Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
+ }
+
+ return retval;
+ }
+
+ private Spanned fromHtml(String source) {
+ if (source != null)
+ return Html.fromHtml(source);
+ else
+ return null;
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PermissionUtils.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PermissionUtils.java
new file mode 100644
index 00000000..6aa5c9bf
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PermissionUtils.java
@@ -0,0 +1,55 @@
+package com.adobe.phonegap.push;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class PermissionUtils {
+
+ private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
+
+ public static boolean hasPermission(Context appContext, String appOpsServiceId) throws UnknownError {
+
+ ApplicationInfo appInfo = appContext.getApplicationInfo();
+
+ String pkg = appContext.getPackageName();
+ int uid = appInfo.uid;
+ Class appOpsClass = null;
+ Object appOps = appContext.getSystemService("appops");
+
+ try {
+
+ appOpsClass = Class.forName("android.app.AppOpsManager");
+
+ Method checkOpNoThrowMethod = appOpsClass.getMethod(
+ CHECK_OP_NO_THROW,
+ Integer.TYPE,
+ Integer.TYPE,
+ String.class
+ );
+
+ Field opValue = appOpsClass.getDeclaredField(appOpsServiceId);
+
+ int value = (int) opValue.getInt(Integer.class);
+ Object result = checkOpNoThrowMethod.invoke(appOps, value, uid, pkg);
+
+ return Integer.parseInt(result.toString()) == 0; // AppOpsManager.MODE_ALLOWED
+
+ } catch (ClassNotFoundException e) {
+ throw new UnknownError("class not found");
+ } catch (NoSuchMethodException e) {
+ throw new UnknownError("no such method");
+ } catch (NoSuchFieldException e) {
+ throw new UnknownError("no such field");
+ } catch (InvocationTargetException e) {
+ throw new UnknownError("invocation target");
+ } catch (IllegalAccessException e) {
+ throw new UnknownError("illegal access");
+ }
+
+ }
+
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java
new file mode 100644
index 00000000..37874e04
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java
@@ -0,0 +1,72 @@
+package com.adobe.phonegap.push;
+
+public interface PushConstants {
+ public static final String COM_ADOBE_PHONEGAP_PUSH = "com.adobe.phonegap.push";
+ public static final String REGISTRATION_ID = "registrationId";
+ public static final String FOREGROUND = "foreground";
+ public static final String TITLE = "title";
+ public static final String NOT_ID = "notId";
+ public static final String PUSH_BUNDLE = "pushBundle";
+ public static final String ICON = "icon";
+ public static final String ICON_COLOR = "iconColor";
+ public static final String SOUND = "sound";
+ public static final String SOUND_DEFAULT = "default";
+ public static final String SOUND_RINGTONE = "ringtone";
+ public static final String VIBRATE = "vibrate";
+ public static final String ACTIONS = "actions";
+ public static final String CALLBACK = "callback";
+ public static final String ACTION_CALLBACK = "actionCallback";
+ public static final String DRAWABLE = "drawable";
+ public static final String MSGCNT = "msgcnt";
+ public static final String VIBRATION_PATTERN = "vibrationPattern";
+ public static final String STYLE = "style";
+ public static final String SUMMARY_TEXT = "summaryText";
+ public static final String PICTURE = "picture";
+ public static final String GCM_N = "gcm.n.";
+ public static final String GCM_NOTIFICATION = "gcm.notification";
+ public static final String GCM_NOTIFICATION_BODY = "gcm.notification.body";
+ public static final String UA_PREFIX = "com.urbanairship.push";
+ public static final String PARSE_COM_DATA = "data";
+ public static final String ALERT = "alert";
+ public static final String MESSAGE = "message";
+ public static final String BODY = "body";
+ public static final String SOUNDNAME = "soundname";
+ public static final String LED_COLOR = "ledColor";
+ public static final String PRIORITY = "priority";
+ public static final String IMAGE = "image";
+ public static final String STYLE_INBOX = "inbox";
+ public static final String STYLE_PICTURE = "picture";
+ public static final String STYLE_TEXT = "text";
+ public static final String BADGE = "badge";
+ public static final String INITIALIZE = "init";
+ public static final String SUBSCRIBE = "subscribe";
+ public static final String UNSUBSCRIBE = "unsubscribe";
+ public static final String UNREGISTER = "unregister";
+ public static final String EXIT = "exit";
+ public static final String FINISH = "finish";
+ public static final String HAS_PERMISSION = "hasPermission";
+ public static final String ANDROID = "android";
+ public static final String SENDER_ID = "senderID";
+ public static final String CLEAR_BADGE = "clearBadge";
+ public static final String CLEAR_NOTIFICATIONS = "clearNotifications";
+ public static final String COLDSTART = "coldstart";
+ public static final String ADDITIONAL_DATA = "additionalData";
+ public static final String COUNT = "count";
+ public static final String FROM = "from";
+ public static final String COLLAPSE_KEY = "collapse_key";
+ public static final String FORCE_SHOW = "forceShow";
+ public static final String GCM = "GCM";
+ public static final String CONTENT_AVAILABLE = "content-available";
+ public static final String TOPICS = "topics";
+ public static final String SET_APPLICATION_ICON_BADGE_NUMBER = "setApplicationIconBadgeNumber";
+ public static final String CLEAR_ALL_NOTIFICATIONS = "clearAllNotifications";
+ public static final String VISIBILITY = "visibility";
+ public static final String INLINE_REPLY = "inlineReply";
+ public static final String LOC_KEY = "locKey";
+ public static final String LOC_DATA = "locData";
+ public static final String TWILIO_BODY = "twi_body";
+ public static final String TWILIO_TITLE = "twi_title";
+ public static final String TWILIO_SOUND = "twi_sound";
+ public static final String START_IN_BACKGROUND = "cdvStartInBackground";
+ public static final String FORCE_START = "force-start";
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java
new file mode 100644
index 00000000..23682ac8
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java
@@ -0,0 +1,120 @@
+package com.adobe.phonegap.push;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.support.v4.app.RemoteInput;
+
+
+public class PushHandlerActivity extends Activity implements PushConstants {
+ private static String LOG_TAG = "PushPlugin_PushHandlerActivity";
+
+ /*
+ * this activity will be started if the user touches a notification that we own.
+ * We send it's data off to the push plugin for processing.
+ * If needed, we boot up the main activity to kickstart the application.
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ GCMIntentService gcm = new GCMIntentService();
+
+ Intent intent = getIntent();
+
+ int notId = intent.getExtras().getInt(NOT_ID, 0);
+ Log.d(LOG_TAG, "not id = " + notId);
+ gcm.setNotification(notId, "");
+ super.onCreate(savedInstanceState);
+ Log.v(LOG_TAG, "onCreate");
+ String callback = getIntent().getExtras().getString("callback");
+ Log.d(LOG_TAG, "callback = " + callback);
+ boolean foreground = getIntent().getExtras().getBoolean("foreground", true);
+ boolean startOnBackground = getIntent().getExtras().getBoolean(START_IN_BACKGROUND, false);
+
+ if(!startOnBackground){
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(GCMIntentService.getAppName(this), notId);
+ }
+
+ boolean isPushPluginActive = PushPlugin.isActive();
+ boolean inline = processPushBundle(isPushPluginActive, intent);
+
+ if(inline && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N){
+ foreground = true;
+ }
+
+ Log.d(LOG_TAG, "bringToForeground = " + foreground);
+
+ finish();
+
+ Log.d(LOG_TAG, "isPushPluginActive = " + isPushPluginActive);
+ if (!isPushPluginActive && foreground && inline) {
+ Log.d(LOG_TAG, "forceMainActivityReload");
+ forceMainActivityReload(false);
+ } else if(startOnBackground) {
+ Log.d(LOG_TAG, "startOnBackgroundTrue");
+ forceMainActivityReload(true);
+ } else {
+ Log.d(LOG_TAG, "don't want main activity");
+ }
+ }
+
+ /**
+ * Takes the pushBundle extras from the intent,
+ * and sends it through to the PushPlugin for processing.
+ */
+ private boolean processPushBundle(boolean isPushPluginActive, Intent intent) {
+ Bundle extras = getIntent().getExtras();
+ Bundle remoteInput = null;
+
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+
+ originalExtras.putBoolean(FOREGROUND, false);
+ originalExtras.putBoolean(COLDSTART, !isPushPluginActive);
+ originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK));
+
+ remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString();
+ Log.d(LOG_TAG, "response: " + inputString);
+ originalExtras.putString(INLINE_REPLY, inputString);
+ }
+
+ PushPlugin.sendExtras(originalExtras);
+ }
+ return remoteInput == null;
+ }
+
+ /**
+ * Forces the main activity to re-launch if it's unloaded.
+ */
+ private void forceMainActivityReload(boolean startOnBackground) {
+ PackageManager pm = getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+ if (originalExtras != null) {
+ launchIntent.putExtras(originalExtras);
+ }
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ launchIntent.addFlags(Intent.FLAG_FROM_BACKGROUND);
+ launchIntent.putExtra(START_IN_BACKGROUND, startOnBackground);
+ }
+
+ startActivity(launchIntent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java
new file mode 100644
index 00000000..eaa39a48
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java
@@ -0,0 +1,27 @@
+package com.adobe.phonegap.push;
+
+import android.content.Intent;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.google.android.gms.iid.InstanceID;
+import com.google.android.gms.iid.InstanceIDListenerService;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+
+public class PushInstanceIDListenerService extends InstanceIDListenerService implements PushConstants {
+ public static final String LOG_TAG = "PushPlugin_PushInstanceIDListenerService";
+
+ @Override
+ public void onTokenRefresh() {
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ String senderID = sharedPref.getString(SENDER_ID, "");
+ if (!"".equals(senderID)) {
+ Intent intent = new Intent(this, RegistrationIntentService.class);
+ startService(intent);
+ }
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java
new file mode 100644
index 00000000..f6faaa2b
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java
@@ -0,0 +1,458 @@
+package com.adobe.phonegap.push;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmPubSub;
+import com.google.android.gms.iid.InstanceID;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.List;
+
+import me.leolin.shortcutbadger.ShortcutBadger;
+
+public class PushPlugin extends CordovaPlugin implements PushConstants {
+
+ public static final String LOG_TAG = "PushPlugin";
+
+ private static CallbackContext pushContext;
+ private static CordovaWebView gWebView;
+ private static List<Bundle> gCachedExtras = Collections.synchronizedList(new ArrayList<Bundle>());
+ private static boolean gForeground = false;
+
+ private static String registration_id = "";
+
+ /**
+ * Gets the application context from cordova's main activity.
+ * @return the application context
+ */
+ private Context getApplicationContext() {
+ return this.cordova.getActivity().getApplicationContext();
+ }
+
+ @Override
+ public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) {
+ Log.v(LOG_TAG, "execute: action=" + action);
+ gWebView = this.webView;
+
+ if (INITIALIZE.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ pushContext = callbackContext;
+ JSONObject jo = null;
+
+ Log.v(LOG_TAG, "execute: data=" + data.toString());
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ String senderID = null;
+
+ try {
+ jo = data.getJSONObject(0).getJSONObject(ANDROID);
+
+ Log.v(LOG_TAG, "execute: jo=" + jo.toString());
+
+ senderID = jo.getString(SENDER_ID);
+
+ Log.v(LOG_TAG, "execute: senderID=" + senderID);
+
+ String savedSenderID = sharedPref.getString(SENDER_ID, "");
+ registration_id = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM);
+
+ if (!"".equals(registration_id)) {
+ JSONObject json = new JSONObject().put(REGISTRATION_ID, registration_id);
+
+ Log.v(LOG_TAG, "onRegistered: " + json.toString());
+
+ JSONArray topics = jo.optJSONArray(TOPICS);
+ subscribeToTopics(topics, registration_id);
+
+ PushPlugin.sendEvent( json );
+ } else {
+ callbackContext.error("Empty registration ID received from GCM");
+ return;
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ }
+
+ if (jo != null) {
+ SharedPreferences.Editor editor = sharedPref.edit();
+ try {
+ editor.putString(ICON, jo.getString(ICON));
+ } catch (JSONException e) {
+ Log.d(LOG_TAG, "no icon option");
+ }
+ try {
+ editor.putString(ICON_COLOR, jo.getString(ICON_COLOR));
+ } catch (JSONException e) {
+ Log.d(LOG_TAG, "no iconColor option");
+ }
+
+ boolean clearBadge = jo.optBoolean(CLEAR_BADGE, false);
+ if (clearBadge) {
+ setApplicationIconBadgeNumber(getApplicationContext(), 0);
+ }
+
+ editor.putBoolean(SOUND, jo.optBoolean(SOUND, true));
+ editor.putBoolean(VIBRATE, jo.optBoolean(VIBRATE, true));
+ editor.putBoolean(CLEAR_BADGE, clearBadge);
+ editor.putBoolean(CLEAR_NOTIFICATIONS, jo.optBoolean(CLEAR_NOTIFICATIONS, true));
+ editor.putBoolean(FORCE_SHOW, jo.optBoolean(FORCE_SHOW, false));
+ editor.putString(SENDER_ID, senderID);
+ editor.commit();
+
+ }
+
+ if (!gCachedExtras.isEmpty()) {
+ Log.v(LOG_TAG, "sending cached extras");
+ synchronized(gCachedExtras) {
+ Iterator<Bundle> gCachedExtrasIterator = gCachedExtras.iterator();
+ while (gCachedExtrasIterator.hasNext()) {
+ sendExtras(gCachedExtrasIterator.next());
+ }
+ }
+ gCachedExtras.clear();
+ }
+ }
+ });
+ } else if (UNREGISTER.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ JSONArray topics = data.optJSONArray(0);
+ if (topics != null && !"".equals(registration_id)) {
+ unsubscribeFromTopics(topics, registration_id);
+ } else {
+ InstanceID.getInstance(getApplicationContext()).deleteInstanceID();
+ Log.v(LOG_TAG, "UNREGISTER");
+
+ // Remove shared prefs
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.remove(SOUND);
+ editor.remove(VIBRATE);
+ editor.remove(CLEAR_BADGE);
+ editor.remove(CLEAR_NOTIFICATIONS);
+ editor.remove(FORCE_SHOW);
+ editor.remove(SENDER_ID);
+ editor.commit();
+ }
+
+ callbackContext.success();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (FINISH.equals(action)) {
+ callbackContext.success();
+ } else if (HAS_PERMISSION.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ JSONObject jo = new JSONObject();
+ try {
+ jo.put("isEnabled", PermissionUtils.hasPermission(getApplicationContext(), "OP_POST_NOTIFICATION"));
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jo);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ } catch (UnknownError e) {
+ callbackContext.error(e.getMessage());
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (SET_APPLICATION_ICON_BADGE_NUMBER.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ Log.v(LOG_TAG, "setApplicationIconBadgeNumber: data=" + data.toString());
+ try {
+ setApplicationIconBadgeNumber(getApplicationContext(), data.getJSONObject(0).getInt(BADGE));
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ }
+ callbackContext.success();
+ }
+ });
+ } else if (CLEAR_ALL_NOTIFICATIONS.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ Log.v(LOG_TAG, "clearAllNotifications");
+ clearAllNotifications();
+ callbackContext.success();
+ }
+ });
+ } else if (SUBSCRIBE.equals(action)){
+ // Subscribing for a topic
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ String topic = data.getString(0);
+ subscribeToTopic(topic, registration_id);
+ callbackContext.success();
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (UNSUBSCRIBE.equals(action)){
+ // un-subscribing for a topic
+ cordova.getThreadPool().execute(new Runnable(){
+ public void run() {
+ try {
+ String topic = data.getString(0);
+ unsubscribeFromTopic(topic, registration_id);
+ callbackContext.success();
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else {
+ Log.e(LOG_TAG, "Invalid action : " + action);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void sendEvent(JSONObject _json) {
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, _json);
+ pluginResult.setKeepCallback(true);
+ if (pushContext != null) {
+ pushContext.sendPluginResult(pluginResult);
+ }
+ }
+
+ public static void sendError(String message) {
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, message);
+ pluginResult.setKeepCallback(true);
+ if (pushContext != null) {
+ pushContext.sendPluginResult(pluginResult);
+ }
+ }
+
+ /*
+ * Sends the pushbundle extras to the client application.
+ * If the client application isn't currently active, it is cached for later processing.
+ */
+ public static void sendExtras(Bundle extras) {
+ if (extras != null) {
+ if (gWebView != null) {
+ sendEvent(convertBundleToJson(extras));
+ } else {
+ Log.v(LOG_TAG, "sendExtras: caching extras to send at a later time.");
+ gCachedExtras.add(extras);
+ }
+ }
+ }
+
+ public static void setApplicationIconBadgeNumber(Context context, int badgeCount) {
+ if (badgeCount > 0) {
+ ShortcutBadger.applyCount(context, badgeCount);
+ } else {
+ ShortcutBadger.removeCount(context);
+ }
+ }
+
+ @Override
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ gForeground = true;
+ }
+
+ @Override
+ public void onPause(boolean multitasking) {
+ super.onPause(multitasking);
+ gForeground = false;
+
+ SharedPreferences prefs = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ if (prefs.getBoolean(CLEAR_NOTIFICATIONS, true)) {
+ clearAllNotifications();
+ }
+ }
+
+ @Override
+ public void onResume(boolean multitasking) {
+ super.onResume(multitasking);
+ gForeground = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ gForeground = false;
+ gWebView = null;
+ }
+
+ private void clearAllNotifications() {
+ final NotificationManager notificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+
+ /**
+ * Transform `topic name` to `topic path`
+ * Normally, the `topic` inputed from end-user is `topic name` only.
+ * We should convert them to GCM `topic path`
+ * Example:
+ * when topic name = 'my-topic'
+ * then topic path = '/topics/my-topic'
+ *
+ * @param String topic The topic name
+ * @return The topic path
+ */
+ private String getTopicPath(String topic)
+ {
+ return "/topics/" + topic;
+ }
+
+ private void subscribeToTopics(JSONArray topics, String registrationToken) throws IOException {
+ if (topics != null) {
+ String topic = null;
+ for (int i=0; i<topics.length(); i++) {
+ topic = topics.optString(i, null);
+ subscribeToTopic(topic, registrationToken);
+ }
+ }
+ }
+
+ private void subscribeToTopic(String topic, String registrationToken) throws IOException
+ {
+ try {
+ if (topic != null) {
+ Log.d(LOG_TAG, "Subscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).subscribe(registrationToken, getTopicPath(topic), null);
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to subscribe to topic: " + topic, e);
+ throw e;
+ }
+ }
+
+ private void unsubscribeFromTopics(JSONArray topics, String registrationToken) {
+ if (topics != null) {
+ String topic = null;
+ for (int i=0; i<topics.length(); i++) {
+ try {
+ topic = topics.optString(i, null);
+ if (topic != null) {
+ Log.d(LOG_TAG, "Unsubscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).unsubscribe(registrationToken, getTopicPath(topic));
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to unsubscribe to topic: " + topic, e);
+ }
+ }
+ }
+ }
+
+ private void unsubscribeFromTopic(String topic, String registrationToken) throws IOException
+ {
+ try {
+ if (topic != null) {
+ Log.d(LOG_TAG, "Unsubscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).unsubscribe(registrationToken, getTopicPath(topic));
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to unsubscribe to topic: " + topic, e);
+ throw e;
+ }
+ }
+
+ /*
+ * serializes a bundle to JSON.
+ */
+ private static JSONObject convertBundleToJson(Bundle extras) {
+ Log.d(LOG_TAG, "convert extras to json");
+ try {
+ JSONObject json = new JSONObject();
+ JSONObject additionalData = new JSONObject();
+
+ // Add any keys that need to be in top level json to this set
+ HashSet<String> jsonKeySet = new HashSet();
+ Collections.addAll(jsonKeySet, TITLE,MESSAGE,COUNT,SOUND,IMAGE);
+
+ Iterator<String> it = extras.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ Object value = extras.get(key);
+
+ Log.d(LOG_TAG, "key = " + key);
+
+ if (jsonKeySet.contains(key)) {
+ json.put(key, value);
+ }
+ else if (key.equals(COLDSTART)) {
+ additionalData.put(key, extras.getBoolean(COLDSTART));
+ }
+ else if (key.equals(FOREGROUND)) {
+ additionalData.put(key, extras.getBoolean(FOREGROUND));
+ }
+ else if ( value instanceof String ) {
+ String strValue = (String)value;
+ try {
+ // Try to figure out if the value is another JSON object
+ if (strValue.startsWith("{")) {
+ additionalData.put(key, new JSONObject(strValue));
+ }
+ // Try to figure out if the value is another JSON array
+ else if (strValue.startsWith("[")) {
+ additionalData.put(key, new JSONArray(strValue));
+ }
+ else {
+ additionalData.put(key, value);
+ }
+ } catch (Exception e) {
+ additionalData.put(key, value);
+ }
+ }
+ } // while
+
+ json.put(ADDITIONAL_DATA, additionalData);
+ Log.v(LOG_TAG, "extrasToJSON: " + json.toString());
+
+ return json;
+ }
+ catch( JSONException e) {
+ Log.e(LOG_TAG, "extrasToJSON: JSON exception");
+ }
+ return null;
+ }
+
+ public static boolean isInForeground() {
+ return gForeground;
+ }
+
+ public static boolean isActive() {
+ return gWebView != null;
+ }
+
+ protected static void setRegistrationID(String token) {
+ registration_id = token;
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java
new file mode 100644
index 00000000..b181e88e
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java
@@ -0,0 +1,38 @@
+package com.adobe.phonegap.push;
+
+import android.content.Context;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+
+import java.io.IOException;
+
+public class RegistrationIntentService extends IntentService implements PushConstants {
+ public static final String LOG_TAG = "PushPlugin_RegistrationIntentService";
+
+ public RegistrationIntentService() {
+ super(LOG_TAG);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+
+ try {
+ InstanceID instanceID = InstanceID.getInstance(this);
+ String senderID = sharedPreferences.getString(SENDER_ID, "");
+ String token = instanceID.getToken(senderID,
+ GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+ PushPlugin.setRegistrationID(token);
+ Log.i(LOG_TAG, "new GCM Registration Token: " + token);
+
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Failed to complete token refresh", e);
+ }
+ }
+}