diff --git a/gradle/include/AttachMacros.h b/gradle/include/AttachMacros.h
new file mode 100644
index 00000000..ef4c4b4c
--- /dev/null
+++ b/gradle/include/AttachMacros.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include
+
+static __inline__ void AttachLog(const char *file, int lineNumber, const char *funcName, NSString* format, ...)
+{
+ va_list argList;
+ va_start(argList, format);
+ NSString* formattedMessage = [[NSString alloc] initWithFormat: format arguments: argList];
+ va_end(argList);
+ NSLog(@"[AttachLog] %@", formattedMessage);
+
+ static NSDateFormatter* dateFormatter;
+ if (!dateFormatter) {
+ dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
+ [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
+ }
+ fprintf(stderr, "[AttachLog] %s %s:%3d %s\n", [[dateFormatter stringFromDate:[NSDate date]] UTF8String], funcName, lineNumber, [formattedMessage UTF8String]);
+ [formattedMessage release];
+}
+
+#define AttachLog(MSG, ...) AttachLog(__FILE__, __LINE__, __PRETTY_FUNCTION__, MSG, ## __VA_ARGS__ )
diff --git a/gradle/native-build.gradle b/gradle/native-build.gradle
index 4133d535..8c4e0eb0 100644
--- a/gradle/native-build.gradle
+++ b/gradle/native-build.gradle
@@ -23,7 +23,10 @@ ext.nativeBuild = { buildDir, projectDir, name, os ->
}
def JAVAHOME = System.getenv("JAVA_HOME")
- def includeFlags = "-I$JAVAHOME/include"
+ def includeFlags = [
+ "-I$JAVAHOME/include",
+ "-I$projectDir/../../gradle/include",
+ ]
def osIncludeFlags = ""
if (os == "ios") {
@@ -101,6 +104,12 @@ ext.nativeBuild = { buildDir, projectDir, name, os ->
args lipoArgs
}
+ println("native build for $name finished")
+ File n = new File(lipoOutput)
+ if (n.exists()) {
+ println "Adding $n to native jar"
+ n
+ }
} else {
// TODO
def compileOutput = "$buildDir/native/$os"
@@ -111,19 +120,18 @@ ext.nativeBuild = { buildDir, projectDir, name, os ->
def cargs = [
"-c", includeFlags, osIncludeFlags, sharedSources, osSources
].flatten()
-
+
exec {
executable "/usr/bin/gcc"
args cargs
workingDir compileOutput
}
- }
-
- println("native build for $name finished")
- File n = new File("$buildDir/native/${os}")
- if (n.exists()) {
- println "Adding lib${name} to native jar"
- fileTree("$buildDir/native/${os}").filter { it.isFile() }.files
- .first()
+ // TODO
+ File n = new File("$buildDir/native/${os}")
+ if (n.exists()) {
+ println "Adding lib${name} to native jar"
+ fileTree("$buildDir/native/${os}").filter { it.isFile() }.files
+ .first()
+ }
}
}
\ No newline at end of file
diff --git a/modules/accelerometer/build.gradle b/modules/accelerometer/build.gradle
index 7e86f7b5..f229a630 100644
--- a/modules/accelerometer/build.gradle
+++ b/modules/accelerometer/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(':util')
+ implementation project(":lifecycle")
}
ext.description = 'Common API to access accelerometer features'
\ No newline at end of file
diff --git a/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/DummyAccelerometerService.java b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/DummyAccelerometerService.java
index aa509351..fe5c9c31 100644
--- a/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/DummyAccelerometerService.java
+++ b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/DummyAccelerometerService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.accelerometer.impl;
import com.gluonhq.attach.accelerometer.AccelerometerService;
diff --git a/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/IOSAccelerometerService.java b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/IOSAccelerometerService.java
new file mode 100644
index 00000000..dedc9a7b
--- /dev/null
+++ b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/impl/IOSAccelerometerService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.accelerometer.impl;
+
+import com.gluonhq.attach.accelerometer.Acceleration;
+import com.gluonhq.attach.accelerometer.AccelerometerService;
+import com.gluonhq.attach.lifecycle.LifecycleEvent;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+public class IOSAccelerometerService implements AccelerometerService {
+
+ static {
+ System.loadLibrary("Accelerometer");
+ initAccelerometer();
+ }
+
+ private static ReadOnlyObjectWrapper acceleration;
+
+ public IOSAccelerometerService() {
+ acceleration = new ReadOnlyObjectWrapper<>();
+
+ LifecycleService.create().ifPresent(l -> {
+ l.addListener(LifecycleEvent.PAUSE, IOSAccelerometerService::stopObserver);
+ l.addListener(LifecycleEvent.RESUME, () -> startObserver(FILTER_GRAVITY, FREQUENCY));
+ });
+ startObserver(FILTER_GRAVITY, FREQUENCY);
+ }
+
+ @Override
+ public Acceleration getCurrentAcceleration() {
+ return acceleration.get();
+ }
+
+ @Override
+ public ReadOnlyObjectProperty accelerationProperty() {
+ return acceleration.getReadOnlyProperty();
+ }
+
+ // native
+ private static native void initAccelerometer();
+ private static native void startObserver(boolean filterGravity, int rateInMillis);
+ private static native void stopObserver();
+
+ // callback
+ private static void notifyAcceleration(double x, double y, double z, double t) {
+ Acceleration a = new Acceleration(x, y, z, toLocalDateTime(t));
+ Platform.runLater(() -> acceleration.setValue(a));
+ }
+
+ private static LocalDateTime toLocalDateTime(double t) {
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli((long) t), ZoneId.systemDefault());
+ }
+}
diff --git a/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/package-info.java b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/package-info.java
index e3f2b00b..6cb12e60 100644
--- a/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/package-info.java
+++ b/modules/accelerometer/src/main/java/com/gluonhq/attach/accelerometer/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Accelerometer plugin,
+ * Primary API package for Attach - Accelerometer plugin,
* contains the interface {@link com.gluonhq.attach.accelerometer.AccelerometerService} and related classes.
*/
package com.gluonhq.attach.accelerometer;
\ No newline at end of file
diff --git a/modules/accelerometer/src/main/java/module-info.java b/modules/accelerometer/src/main/java/module-info.java
index c096bfbe..0e85a140 100644
--- a/modules/accelerometer/src/main/java/module-info.java
+++ b/modules/accelerometer/src/main/java/module-info.java
@@ -27,8 +27,9 @@
*/
module com.gluonhq.attach.accelerometer {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.lifecycle;
exports com.gluonhq.attach.accelerometer;
exports com.gluonhq.attach.accelerometer.impl to com.gluonhq.attach.util;
diff --git a/modules/accelerometer/src/main/native/ios/Accelerometer.h b/modules/accelerometer/src/main/native/ios/Accelerometer.h
new file mode 100644
index 00000000..160290d9
--- /dev/null
+++ b/modules/accelerometer/src/main/native/ios/Accelerometer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface Accelerometer : UIViewController {}
+ @property (strong, nonatomic) CMMotionManager *motionManager;
+ - (void) startObserver;
+ - (void) stopObserver;
+@end
+
+void sendAcceleration(CMAccelerometerData *accelerometerData);
diff --git a/modules/accelerometer/src/main/native/ios/Accelerometer.m b/modules/accelerometer/src/main/native/ios/Accelerometer.m
new file mode 100644
index 00000000..ff895b77
--- /dev/null
+++ b/modules/accelerometer/src/main/native/ios/Accelerometer.m
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Accelerometer.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Accelerometer(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int AccelerometerInited = 0;
+
+// Accelerometer
+jclass mat_jAccelerometerServiceClass;
+jmethodID mat_jAccelerometerService_notifyAcceleration = 0;
+Accelerometer *_accelerometer;
+BOOL filterGravity;
+double rate = 0.01;
+double gravity[3];
+double alpha = 0.8;
+double offset = 0;
+double g = 9.81;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_accelerometer_impl_IOSAccelerometerService_initAccelerometer
+(JNIEnv *env, jclass jClass)
+{
+ if (AccelerometerInited)
+ {
+ return;
+ }
+ AccelerometerInited = 1;
+
+ mat_jAccelerometerServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/accelerometer/impl/IOSAccelerometerService"));
+ mat_jAccelerometerService_notifyAcceleration = (*env)->GetStaticMethodID(env, mat_jAccelerometerServiceClass, "notifyAcceleration", "(DDDD)V");
+
+ _accelerometer = [[Accelerometer alloc] init];
+
+ NSTimeInterval uptime = [NSProcessInfo processInfo].systemUptime;
+ NSTimeInterval nowTimeIntervalSince1970 = [[NSDate date] timeIntervalSince1970];
+ offset = nowTimeIntervalSince1970 - uptime;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_accelerometer_impl_IOSAccelerometerService_startObserver
+(JNIEnv *env, jclass jClass, jboolean jfilterGravity, jint jfrequency)
+{
+ filterGravity = jfilterGravity;
+ if (jfrequency > 0) {
+ rate = 1.0 / ((double) jfrequency);
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_accelerometer startObserver];
+ });
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_accelerometer_impl_IOSAccelerometerService_stopObserver
+(JNIEnv *env, jclass jClass)
+{
+ [_accelerometer stopObserver];
+ return;
+}
+
+void sendAcceleration(CMAccelerometerData *accelerometerData) {
+ double x = accelerometerData.acceleration.x * g;
+ double y = accelerometerData.acceleration.y * g;
+ double z = accelerometerData.acceleration.z * g;
+
+ if (filterGravity) {
+ // filter to remove gravity
+ gravity[0] = alpha * gravity[0] + (1 - alpha) * x;
+ gravity[1] = alpha * gravity[1] + (1 - alpha) * y;
+ gravity[2] = alpha * gravity[2] + (1 - alpha) * z;
+
+ x -= gravity[0];
+ y -= gravity[1];
+ z -= gravity[2];
+ }
+ double t = (accelerometerData.timestamp + offset) * 1000;
+ (*env)->CallStaticVoidMethod(env, mat_jAccelerometerServiceClass, mat_jAccelerometerService_notifyAcceleration, x, y, z, t);
+}
+
+@implementation Accelerometer
+
+- (void) startObserver
+{
+
+ if (!self.motionManager) {
+ self.motionManager = [[CMMotionManager alloc] init];
+ }
+
+ if (self.motionManager.accelerometerAvailable)
+ {
+ self.motionManager.accelerometerUpdateInterval = rate; // in seconds
+ [self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
+ withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
+ sendAcceleration(accelerometerData);
+ }];
+ } else
+ {
+ AttachLog(@"Error: No Accelerometer or Gyroscope Available");
+ }
+
+}
+
+- (void) stopObserver
+{
+ if (self.motionManager)
+ {
+ [self.motionManager stopAccelerometerUpdates];
+ }
+}
+
+@end
+
diff --git a/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/AudioRecordingService.java b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/AudioRecordingService.java
index b34a1575..321a044b 100644
--- a/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/AudioRecordingService.java
+++ b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/AudioRecordingService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Gluon
+ * Copyright (c) 2017, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/impl/IOSAudioRecordingService.java b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/impl/IOSAudioRecordingService.java
new file mode 100644
index 00000000..7e5f91c6
--- /dev/null
+++ b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/impl/IOSAudioRecordingService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.audiorecording.impl;
+
+import com.gluonhq.attach.util.Constants;
+import javafx.application.Platform;
+
+import java.util.function.Function;
+
+
+public class IOSAudioRecordingService extends DefaultAudioRecordingService {
+
+ static {
+ System.loadLibrary("AudioRecording");
+ initAudioRecording();
+ }
+ private static Function addChunk;
+
+ public IOSAudioRecordingService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ }
+
+ @Override
+ protected void start(float sampleRate, int sampleSizeInBits, int channels, int chunkRecordTime, Function addChunk) {
+ startAudioRecording(getAudioFolder().getName(), sampleRate, sampleSizeInBits, channels, chunkRecordTime);
+ IOSAudioRecordingService.addChunk = addChunk;
+ }
+
+ @Override
+ protected void stop() {
+ stopAudioRecording();
+ }
+
+ // native
+ private static native void initAudioRecording();
+ private native void startAudioRecording(String audioFolderName, float sampleRate, int sampleSizeInBits, int channels, int chunkRecordTime);
+ private native void stopAudioRecording();
+ private static native void enableDebug();
+
+ // callback
+ private static void notifyRecordingStatus(boolean value) {
+ Platform.runLater(() -> updateRecordingStatus(value));
+ }
+
+ private static void notifyRecordingChunk(String file) {
+ Platform.runLater(() -> addChunk.apply(file));
+ }
+
+}
\ No newline at end of file
diff --git a/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/package-info.java b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/package-info.java
index 56c19f99..c6e15dbd 100644
--- a/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/package-info.java
+++ b/modules/audio-recording/src/main/java/com/gluonhq/attach/audiorecording/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Audio Recording plugin,
+ * Primary API package for Attach - Audio Recording plugin,
* contains the interface {@link com.gluonhq.attach.audiorecording.AudioRecordingService} and related classes.
*/
package com.gluonhq.attach.audiorecording;
\ No newline at end of file
diff --git a/modules/audio-recording/src/main/native/ios/AudioRecording.h b/modules/audio-recording/src/main/native/ios/AudioRecording.h
new file mode 100644
index 00000000..ea8c17ae
--- /dev/null
+++ b/modules/audio-recording/src/main/native/ios/AudioRecording.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface AudioRecording : UIViewController
+{
+}
+
+ @property (nonatomic, retain) AVAudioRecorder *avAudioRecorderController;
+ @property (strong, nonatomic) NSTimer *timer;
+ @property BOOL restart;
+
+ - (void) playAudioRecorder;
+ - (void) startRecording:(AVAudioSession *)session;
+ - (void) stopAudioRecorder;
+ - (void) restartAudioRecorder;
+@end
+
+void sendRecordingStatus(BOOL recording);
+void sendRecordingChunk(NSString *fileName);
\ No newline at end of file
diff --git a/modules/audio-recording/src/main/native/ios/AudioRecording.m b/modules/audio-recording/src/main/native/ios/AudioRecording.m
new file mode 100644
index 00000000..5d0360c0
--- /dev/null
+++ b/modules/audio-recording/src/main/native/ios/AudioRecording.m
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AudioRecording.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_AudioRecording(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int audioRecordingInited = 0;
+
+jclass mat_jAudioRecordingServiceClass;
+jmethodID mat_jAudioRecordingService_notifyRecordingStatus = 0;
+jmethodID mat_jAudioRecordingService_notifyRecordingChunk = 0;
+
+// AudioRecording
+AudioRecording *_audioRecording;
+
+NSMutableString *dataPath;
+NSMutableDictionary *recordSettings;
+NSDateFormatter *format;
+
+double timerInterval = 20.0f;
+int counter = 0;
+BOOL debugAudioRecording;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_audiorecording_impl_IOSAudioRecordingService_initAudioRecording
+(JNIEnv *env, jclass jClass)
+{
+ if (audioRecordingInited)
+ {
+ return;
+ }
+ audioRecordingInited = 1;
+
+ mat_jAudioRecordingServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/audiorecording/impl/IOSAudioRecordingService"));
+ mat_jAudioRecordingService_notifyRecordingStatus = (*env)->GetStaticMethodID(env, mat_jAudioRecordingServiceClass, "notifyRecordingStatus", "(Z)V");
+ mat_jAudioRecordingService_notifyRecordingChunk = (*env)->GetStaticMethodID(env, mat_jAudioRecordingServiceClass, "notifyRecordingChunk", "(Ljava/lang/String;)V");
+
+ AttachLog(@"Initialize IOSAudioRecordingService");
+
+ format = [[NSDateFormatter alloc] init];
+ [format setDateFormat:@"yyyy-MM-dd.HH-mm-ss.SSS"];
+
+ _audioRecording = [[AudioRecording alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_audiorecording_impl_IOSAudioRecordingService_startAudioRecording
+(JNIEnv *env, jclass jClass, jstring jAudioFolderName, jfloat sampleRate, jint sampleSizeInBits, jint channels, jint chunkRecordTime)
+{
+ dataPath = [[NSMutableString alloc] init];
+
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ const jchar *charsAudioFolder = (*env)->GetStringChars(env, jAudioFolderName, NULL);
+ NSString *audioFolder = [NSString stringWithCharacters:(UniChar *)charsAudioFolder length:(*env)->GetStringLength(env, jAudioFolderName)];
+ (*env)->ReleaseStringChars(env, jAudioFolderName, charsAudioFolder);
+
+ [dataPath setString:[[paths objectAtIndex:0] stringByAppendingPathComponent:audioFolder]];
+
+ timerInterval = chunkRecordTime;
+
+ // Define the recorder setting
+ recordSettings = [[NSMutableDictionary alloc] init];
+
+ [recordSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
+ [recordSettings setValue:[NSNumber numberWithFloat:sampleRate] forKey:AVSampleRateKey];
+ [recordSettings setValue:[NSNumber numberWithInt:AVAudioQualityMin] forKey:AVEncoderAudioQualityKey];
+ [recordSettings setValue:[NSNumber numberWithInt:sampleSizeInBits] forKey:AVEncoderBitRateKey];
+ [recordSettings setValue:[NSNumber numberWithInt:channels] forKey:AVNumberOfChannelsKey];
+
+ AttachLog(@"Start Recording");
+ [_audioRecording playAudioRecorder];
+
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_audiorecording_impl_IOSAudioRecordingService_stopAudioRecording
+(JNIEnv *env, jclass jClass)
+{
+ AttachLog(@"Stop Recording");
+ [_audioRecording stopAudioRecorder];
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_audiorecording_impl_IOSAudioRecordingService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugAudioRecording = YES;
+}
+
+void sendRecordingStatus(BOOL recording) {
+ AttachLog(@"Recording Status is %s", recording ? "true" : "false");
+ (*env)->CallStaticVoidMethod(env, mat_jAudioRecordingServiceClass, mat_jAudioRecordingService_notifyRecordingStatus, recording ? JNI_TRUE : JNI_FALSE);
+}
+
+void sendRecordingChunk(NSString *fileName) {
+ if (debugAudioRecording) {
+ AttachLog(@"Send chunk file: %@", fileName);
+ }
+ const char *chunkChars = [fileName UTF8String];
+ jstring arg = (*env)->NewStringUTF(env, chunkChars);
+ (*env)->CallStaticVoidMethod(env, mat_jAudioRecordingServiceClass, mat_jAudioRecordingService_notifyRecordingChunk, arg);
+ (*env)->DeleteLocalRef(env, arg);
+}
+
+@implementation AudioRecording
+@synthesize restart;
+
+- (void)playAudioRecorder
+{
+ if(_avAudioRecorderController)
+ {
+ _avAudioRecorderController = nil;
+ }
+
+ // set audio session
+ AVAudioSession *session = [AVAudioSession sharedInstance];
+ [session setCategory:AVAudioSessionCategoryRecord error:nil];
+ [session setActive:YES error:nil];
+
+ // Check mic permission
+ [session requestRecordPermission:^(BOOL granted) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (granted) {
+ [self logMessage:@"Microphone enabled"];
+ [self startRecording:session];
+ }
+ else {
+ AttachLog(@"Microphone disabled");
+ }
+ });
+ }];
+}
+
+- (void) startRecording:(AVAudioSession *)session
+{
+ // Initiate and prepare the recorder
+ counter = 0;
+ NSString *recorderFilePath = [NSString stringWithFormat:@"%@/audioFile.wav", dataPath];
+ [self logMessage:@"recorderFilePath: %@",recorderFilePath];
+ NSURL *outputFileURL = [NSURL fileURLWithPath:recorderFilePath];
+
+ NSError *error = nil;
+ _avAudioRecorderController = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSettings error:&error];
+ _avAudioRecorderController.delegate = self;
+ _avAudioRecorderController.meteringEnabled = YES;
+ if ([_avAudioRecorderController prepareToRecord] == YES) {
+ restart = NO;
+ [self logMessage:@"starting timer"];
+ _timer = [[NSTimer alloc] initWithFireDate: [NSDate dateWithTimeIntervalSinceNow: timerInterval]
+ interval: timerInterval
+ target: self
+ selector:@selector(restartAudioRecorder)
+ userInfo:nil repeats:YES];
+
+ NSRunLoop *runner = [NSRunLoop currentRunLoop];
+ [runner addTimer:_timer forMode: NSDefaultRunLoopMode];
+
+ [_avAudioRecorderController record];
+ [self logMessage:@"recording started"];
+ sendRecordingStatus(YES);
+ } else {
+ AttachLog(@"Error recorder: %@ %@ %@", [error domain], [error localizedDescription], [[error userInfo] description]);
+ AttachLog(@"recording failed");
+ }
+}
+
+- (void)restartAudioRecorder
+{
+ if(!_avAudioRecorderController)
+ {
+ return;
+ }
+ [self logMessage:@"restart recorder"];
+ restart = YES;
+ [_avAudioRecorderController stop];
+}
+
+- (void)stopAudioRecorder
+{
+ if(!_avAudioRecorderController)
+ {
+ return;
+ }
+ [self logMessage:@"stop recorder"];
+ restart = NO;
+ [_avAudioRecorderController stop];
+}
+
+- (void) audioRecorderDidFinishRecording:(AVAudioRecorder *)avrecorder successfully:(BOOL)flag {
+ NSString *recorderFilePath = [NSString stringWithFormat:@"%@/audioFile.wav", dataPath];
+ NSString *recorderFileName = [NSString stringWithFormat:@"audioFile-%03d-%@.wav", counter, [format stringFromDate:NSDate.date]];
+ NSString *recorderFileFinalPath = [NSString stringWithFormat:@"%@/%@", dataPath, recorderFileName];
+ counter = counter + 1;
+ [self logMessage:@"Copy to recorderFileFinalPath: %@",recorderFileFinalPath];
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error;
+ BOOL result = [fileManager copyItemAtPath:recorderFilePath toPath:recorderFileFinalPath error:&error];
+ if(!result) {
+ AttachLog(@"Error copying file: %@", error);
+ }
+ sendRecordingChunk(recorderFileName);
+ if (restart) {
+ [fileManager release];
+ [self logMessage:@"stopped and restarted"];
+
+ if(_avAudioRecorderController)
+ {
+ // resume recording again
+ [_avAudioRecorderController record];
+ }
+
+ } else {
+ [self logMessage:@"Finished recording"];
+ [fileManager removeItemAtPath:recorderFilePath error:&error];
+ [fileManager release];
+
+ if(_timer)
+ {
+ [_timer invalidate];
+ [_timer release];
+ }
+ if(_avAudioRecorderController)
+ {
+ [recordSettings release];
+ [_avAudioRecorderController release];
+ sendRecordingStatus(NO);
+ }
+ }
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugAudioRecording)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv([@"[Debug] " stringByAppendingString:format], args);
+ va_end(args);
+ }
+}
+@end
\ No newline at end of file
diff --git a/modules/augmented-reality/build.gradle b/modules/augmented-reality/build.gradle
index 66e77309..ff6867a6 100644
--- a/modules/augmented-reality/build.gradle
+++ b/modules/augmented-reality/build.gradle
@@ -1,7 +1,7 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
diff --git a/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/AugmentedRealityService.java b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/AugmentedRealityService.java
index 10c55123..c8e27efe 100644
--- a/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/AugmentedRealityService.java
+++ b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/AugmentedRealityService.java
@@ -27,8 +27,11 @@
*/
package com.gluonhq.attach.ar;
+import com.gluonhq.attach.util.Services;
import javafx.beans.property.ReadOnlyBooleanProperty;
+import java.util.Optional;
+
/**
* The Augmented Reality Service allows accesing the native AR kit, if it is available.
*
@@ -125,10 +128,18 @@
*/
public interface AugmentedRealityService {
- public enum Availability {
+ enum Availability {
AR_NOT_SUPPORTED, ARCORE_NOT_INSTALLED, ARCORE_OUTDATED, IOS_NOT_UPDATED, AR_SUPPORTED
}
-
+
+ /**
+ * Returns an instance of {@link AugmentedRealityService}.
+ * @return An instance of {@link AugmentedRealityService}.
+ */
+ static Optional create() {
+ return Services.get(AugmentedRealityService.class);
+ }
+
/**
* Checks if device supports AR
* @param afterInstall action that can be performed if AR is installed
@@ -146,7 +157,7 @@ public enum Availability {
* {@code /src/ios/assets/} for iOS, while for Android these can be placed
* under {@code /src/android/assets/} or {@code /src/main/resources/assets/}.
*
- * @param model
+ * @param model the entity model
*/
void setModel(ARModel model);
@@ -158,7 +169,7 @@ public enum Availability {
/**
* Shows debug information
*
- * @param enable
+ * @param enable set to true to get verbose output
*/
void debugAR(boolean enable);
diff --git a/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/impl/IOSAugmentedRealityService.java b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/impl/IOSAugmentedRealityService.java
new file mode 100644
index 00000000..7a1b3c9c
--- /dev/null
+++ b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/impl/IOSAugmentedRealityService.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.ar.impl;
+
+import com.gluonhq.attach.ar.ARModel;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class IOSAugmentedRealityService extends DefaultAugmentedRealityService {
+
+ private static final Logger LOG = Logger.getLogger(IOSAugmentedRealityService.class.getName());
+
+ private static final int CHECK_AR;
+ private static final int ARKIT_NOT_SUPPORTED = 0;
+ private static final int IOS_NOT_UPDATED = 1;
+ private static final int ARKIT_SUPPORTED = 2;
+
+ private static final ReadOnlyBooleanWrapper CANCELLED = new ReadOnlyBooleanWrapper();
+
+ static {
+ System.loadLibrary("AugmentedReality");
+ CHECK_AR = initAR();
+ }
+
+ public IOSAugmentedRealityService() {
+ if (debug) {
+ enableDebug();
+ }
+ }
+
+ @Override
+ public Availability checkAR(Runnable afterInstall) {
+ if (CHECK_AR == ARKIT_NOT_SUPPORTED) {
+ return Availability.AR_NOT_SUPPORTED;
+ } else if (CHECK_AR == IOS_NOT_UPDATED) {
+ return Availability.IOS_NOT_UPDATED;
+ }
+ return Availability.AR_SUPPORTED;
+ }
+
+ @Override
+ public void setModel(ARModel model) {
+ setARModel(model.getObjFilename(), model.getScale());
+ }
+
+ @Override
+ public void showAR() {
+ if (debug) LOG.log(Level.INFO, "Show AR...");
+ CANCELLED.setValue(false);
+ showNativeAR();
+ }
+
+ @Override
+ public void debugAR(boolean enable) {
+ if (enable) {
+ enableDebugAR();
+ }
+ }
+
+ @Override
+ public ReadOnlyBooleanProperty cancelled() {
+ return CANCELLED.getReadOnlyProperty();
+ }
+
+ // native
+ private static native int initAR(); // init IDs for java callbacks from native
+ private native void showNativeAR();
+ private native void setARModel(String objFileName, double scale);
+
+ private static native void enableDebug();
+ private static native void enableDebugAR();
+
+ private static void notifyCancel() {
+ if (CANCELLED != null && ! CANCELLED.get()) {
+ Platform.runLater(() -> CANCELLED.set(true));
+ }
+ }
+
+}
diff --git a/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/package-info.java b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/package-info.java
index 4dc809c9..b16f7643 100644
--- a/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/package-info.java
+++ b/modules/augmented-reality/src/main/java/com/gluonhq/attach/ar/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Augmented Reality plugin,
+ * Primary API package for Attach - Augmented Reality plugin,
* contains the interface {@link com.gluonhq.attach.ar.AugmentedRealityService} and related classes.
*/
package com.gluonhq.attach.ar;
\ No newline at end of file
diff --git a/modules/augmented-reality/src/main/java/module-info.java b/modules/augmented-reality/src/main/java/module-info.java
index f5d43943..517adff6 100644
--- a/modules/augmented-reality/src/main/java/module-info.java
+++ b/modules/augmented-reality/src/main/java/module-info.java
@@ -27,7 +27,7 @@
*/
module com.gluonhq.attach.audio.recording {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
requires com.gluonhq.attach.storage;
diff --git a/modules/augmented-reality/src/main/native/ios/AugmentedReality.h b/modules/augmented-reality/src/main/native/ios/AugmentedReality.h
new file mode 100644
index 00000000..20ae3d4f
--- /dev/null
+++ b/modules/augmented-reality/src/main/native/ios/AugmentedReality.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+#import
+
+API_AVAILABLE(ios(11.3))
+@interface AugmentedReality : UIViewController
+{
+}
+
+ @property(nonatomic, strong) IBOutlet ARSCNView *sceneView API_AVAILABLE(ios(11.3));
+ @property(nonatomic, strong) IBOutlet UIButton *cancelButton;
+ @property(nonatomic, strong) IBOutlet NSString *modelFileName;
+
+ - (void) showAR;
+ - (void)setARModel:(NSString *)fileName scale:(double)scale;
+ - (void) hideAR;
+@end
+
+void sendCancelled();
\ No newline at end of file
diff --git a/modules/augmented-reality/src/main/native/ios/AugmentedReality.m b/modules/augmented-reality/src/main/native/ios/AugmentedReality.m
new file mode 100644
index 00000000..1a254d3b
--- /dev/null
+++ b/modules/augmented-reality/src/main/native/ios/AugmentedReality.m
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2018, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "AugmentedReality.h"
+
+// JNIEnv *env = NULL;
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_AugmentedReality(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int AugmentedRealityInited = 0;
+
+// AugmentedReality
+jclass mat_jAugmentedRealityServiceClass;
+jmethodID mat_jAugmentedRealityService_notifyCancel = 0;
+
+API_AVAILABLE(ios(11.3))
+AugmentedReality *_ar;
+
+BOOL debugAugmentedReality;
+BOOL enableDebugAugmentedReality;
+
+JNIEXPORT jint JNICALL Java_com_gluonhq_attach_ar_impl_IOSAugmentedRealityService_initAR
+(JNIEnv *myenv, jclass jClass)
+{
+ if (AugmentedRealityInited)
+ {
+ return 0;
+ }
+ AugmentedRealityInited = 1;
+
+ mat_jAugmentedRealityServiceClass = (*myenv)->NewGlobalRef(myenv, (*myenv)->FindClass(myenv, "com/gluonhq/attach/ar/impl/IOSAugmentedRealityService"));
+ mat_jAugmentedRealityService_notifyCancel = (*myenv)->GetStaticMethodID(myenv, mat_jAugmentedRealityServiceClass, "notifyCancel", "()V");
+
+ AttachLog(@"Init AugmentedReality");
+ if (@available(iOS 11.0, *)) { // First of all, ARConfiguration requires iOS 11.0+
+ if (ARConfiguration.isSupported) { // Then, AR requires chip A9+ that supports AR
+ if (@available(iOS 11.3, *)) { // this app uses APIs that require iOS 11.3+
+ AttachLog(@"ARKit is supported and iOS is at least 11.3");
+ _ar = [[AugmentedReality alloc] init];
+ return 2;
+ } else {
+ AttachLog(@"ARKit requires at least 11.3. Please update your device");
+ return 1;
+ }
+ }
+ }
+ AttachLog(@"ARKit is not supported");
+ return 0;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ar_impl_IOSAugmentedRealityService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugAugmentedReality = YES;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ar_impl_IOSAugmentedRealityService_enableDebugAR
+(JNIEnv *env, jclass jClass)
+{
+ enableDebugAugmentedReality = YES;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ar_impl_IOSAugmentedRealityService_showNativeAR
+(JNIEnv *env, jclass jClass)
+{
+ if (@available(iOS 11.3, *)) {
+ if (_ar)
+ {
+ [_ar showAR];
+ }
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ar_impl_IOSAugmentedRealityService_setARModel
+(JNIEnv *env, jclass jClass, jstring jObjFileName, jdouble scale)
+{
+ const jchar *charsObjFileName = (*env)->GetStringChars(env, jObjFileName, NULL);
+ NSString *objFileName = [NSString stringWithCharacters:(UniChar *)charsObjFileName length:(*env)->GetStringLength(env, jObjFileName)];
+ (*env)->ReleaseStringChars(env, jObjFileName, charsObjFileName);
+
+ if (@available(iOS 11.3, *)) {
+ if (_ar)
+ {
+ [_ar setARModel:objFileName scale:scale];
+ }
+ }
+ return;
+}
+
+void sendCancelled() {
+ AttachLog(@"Sending cancel action");
+ (*env)->CallStaticVoidMethod(env, mat_jAugmentedRealityServiceClass, mat_jAugmentedRealityService_notifyCancel);
+}
+
+@implementation AugmentedReality
+
+#pragma mark - Overriding UIViewController
+
+double modelScale = 1.0;
+
+- (BOOL)prefersStatusBarHidden {
+ return YES;
+}
+
+- (void)setARModel:(NSString *)fileName scale:(double)scale
+{
+ self.modelFileName = fileName;
+ modelScale = scale;
+ [self logMessage:@"Set ARModel: %@ %.2f", self.modelFileName, modelScale];
+}
+
+- (void)showAR
+{
+ [self logMessage:@"showing AR"];
+
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+ UIWindow* window = [UIApplication sharedApplication].keyWindow;
+
+ NSArray *views = [window subviews];
+ if(![views count]) {
+ AttachLog(@"views size was 0");
+ return;
+ }
+
+ UIView *_currentView = views[0];
+
+ UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
+ if(!rootViewController)
+ {
+ AttachLog(@"rootViewController was nil");
+ return;
+ }
+
+ // Stop the screen from dimming while we are using the app
+ [UIApplication.sharedApplication setIdleTimerDisabled:YES];
+
+ [self logMessage:@"adding sceneView"];
+ self.sceneView = [[ARSCNView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ self.sceneView.contentMode = UIViewContentModeScaleToFill;
+ self.sceneView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
+ self.sceneView.multipleTouchEnabled = YES;
+ self.sceneView.antialiasingMode = SCNAntialiasingModeMultisampling4X;
+ self.sceneView.autoenablesDefaultLighting = YES;
+ self.sceneView.automaticallyUpdatesLighting = NO;
+
+ self.sceneView.delegate = self;
+ [self logMessage:@"got sceneView %@", self.sceneView];
+ if (enableDebugAugmentedReality) {
+ self.sceneView.showsStatistics = YES;
+ self.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints | ARSCNDebugOptionShowWorldOrigin;
+ }
+ self.sceneView.userInteractionEnabled = YES;
+
+ [self logMessage:@"SceneView: %@", self.sceneView];
+
+ [self logMessage:@"adding scene"];
+ SCNScene *scene = [[SCNScene alloc] init];
+ self.sceneView.scene = scene;
+
+ [self logMessage:@"adding subView"];
+ [_currentView addSubview:self.sceneView];
+ [_currentView bringSubviewToFront:self.sceneView];
+
+
+ ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfiguration new];
+ [configuration setWorldAlignment:ARWorldAlignmentGravity];
+ [configuration setPlaneDetection:ARPlaneDetectionHorizontal];
+ configuration.lightEstimationEnabled = YES;
+
+ [self logMessage:@"run sceneView"];
+ self.sceneView.session = [[ARSession alloc] init];
+ [self.sceneView.session runWithConfiguration:configuration];
+ self.sceneView.session.delegate = self;
+
+ [self logMessage:@"***** Running %@", self.sceneView.session];
+
+ self.cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
+ [self.cancelButton setTitle:@"CANCEL" forState:UIControlStateNormal];
+ [self.cancelButton addTarget:self action:@selector(cancelButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
+ self.cancelButton.frame = CGRectMake(16.0, 44.0, 100.0, 30.0);
+ [self.sceneView addSubview:self.cancelButton];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self startObserver];
+ });
+
+ UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
+ [self.sceneView addGestureRecognizer:singleFingerTap];
+}
+
+- (void)hideAR {
+ [UIApplication.sharedApplication setIdleTimerDisabled:NO];
+ if (! self.sceneView.scene) {
+ [self logMessage:@"Remove nodes"];
+ for (SCNNode *childNode in [[self.sceneView.scene rootNode] childNodes]) {
+ [childNode removeFromParentNode];
+ }
+ }
+ [self logMessage:@"Stop session"];
+ [self.sceneView.session pause];
+
+ [self logMessage:@"Remove sceneView"];
+ [self.sceneView removeFromSuperview];
+ [self stopObserver];
+}
+
+#pragma mark - ARSCNViewDelegate
+
+- (void) renderer:(id)renderer didAddNode:(nonnull SCNNode *)node forAnchor:(nonnull ARAnchor *)anchor {
+ if (enableDebugAugmentedReality && [anchor isKindOfClass:[ARPlaneAnchor class]]) {
+ [self logMessage:@"didAddNode: add plane"];
+ ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
+
+ CGFloat width = planeAnchor.extent.x;
+ CGFloat height = planeAnchor.extent.z;
+ SCNPlane *plane = [SCNPlane planeWithWidth:width height:height];
+
+ plane.materials.firstObject.diffuse.contents =
+ [UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.3f];
+
+ SCNNode *planeNode = [SCNNode nodeWithGeometry:plane];
+
+ CGFloat x = planeAnchor.center.x;
+ CGFloat y = planeAnchor.center.y;
+ CGFloat z = planeAnchor.center.z;
+ planeNode.position = SCNVector3Make(x, y, z);
+ planeNode.eulerAngles = SCNVector3Make(-M_PI / 2, 0, 0);
+
+ [node addChildNode:planeNode];
+ }
+}
+
+- (void)renderer:(id)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
+ if (enableDebugAugmentedReality && [anchor isKindOfClass:[ARPlaneAnchor class]]) {
+ ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
+
+ SCNNode *planeNode = node.childNodes.firstObject;
+ SCNPlane *plane = (SCNPlane *)planeNode.geometry;
+
+ CGFloat width = planeAnchor.extent.x;
+ CGFloat height = planeAnchor.extent.z;
+ plane.width = width;
+ plane.height = height;
+
+ CGFloat x = planeAnchor.center.x;
+ CGFloat y = planeAnchor.center.y;
+ CGFloat z = planeAnchor.center.z;
+ planeNode.position = SCNVector3Make(x, y, z);
+ }
+}
+
+- (void)renderer:(id)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
+ if ([anchor isKindOfClass:[ARPlaneAnchor class]]) {
+ [self logMessage:@"didRemoveNode: remove plane"];
+ SCNNode *planeNode = node.childNodes.firstObject;
+ [planeNode removeFromParentNode];
+ }
+}
+
+- (void)renderer:(id )renderer updateAtTime:(NSTimeInterval)time {
+ ARLightEstimate *estimate = self.sceneView.session.currentFrame.lightEstimate;
+ if (! estimate) {
+ return;
+ }
+ //AttachLog(@"light estimate: %f", estimate.ambientIntensity);
+
+ CGFloat intensity = estimate.ambientIntensity / 1000.0;
+ self.sceneView.scene.lightingEnvironment.intensity = intensity;
+}
+
+- (void)session:(ARSession *)session didFailWithError:(NSError *)error {
+ // Present an error message to the user
+ AttachLog(@"Session error: %@", error);
+}
+
+- (void)sessionWasInterrupted:(ARSession *)session {
+ // Inform the user that the session has been interrupted, for example, by presenting an overlay
+ AttachLog(@"session was interrupted: %@", session);
+}
+
+- (void)sessionInterruptionEnded:(ARSession *)session {
+ // Reset tracking and/or remove existing anchors if consistent tracking is required
+ AttachLog(@"session interruption ended: %@", session);
+}
+
+#pragma mark - ARSessionDelegate
+
+- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
+{
+}
+
+# pragma mark - Actions
+
+- (void)cancelButtonPressed:(UIButton*)sender {
+ [self logMessage:@"Cancel AR session"];
+ [self hideAR];
+ sendCancelled();
+}
+
+//The event handling method
+- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
+{
+ CGPoint touchLocation = [recognizer locationInView:[recognizer.view superview]];
+ [self logMessage:@"TapGesture: %@", NSStringFromCGPoint(touchLocation)];
+
+ NSArray *hitTestResults = [self.sceneView hitTest:touchLocation types:ARHitTestResultTypeExistingPlane |
+ ARHitTestResultTypeExistingPlaneUsingExtent |
+ ARHitTestResultTypeEstimatedHorizontalPlane];
+ [self logMessage:@"HitTestResults: %@", hitTestResults];
+ if (hitTestResults.count > 0) {
+ ARHitTestResult *result = [hitTestResults firstObject];
+ [self logMessage:@"Result %@", result];
+
+ SCNNode *model = [[SCNNode new] autorelease];
+ // Create a new scene
+ if ([self.modelFileName length] > 0) {
+ SCNScene *scene = [SCNScene sceneNamed:self.modelFileName];
+ [self logMessage:@"Adding new scene %@", scene];
+ for (SCNNode *childNode in [[scene rootNode] childNodes]) {
+ [model addChildNode:childNode];
+ }
+ } else {
+ AttachLog(@"No model was set. Use AugmentedRealityService::setModel");
+ }
+ [self logMessage:@"node: %@", model];
+ SCNMatrix4 sc = SCNMatrix4MakeScale(modelScale, modelScale, modelScale);
+ model.transform = SCNMatrix4Mult(SCNMatrix4FromMat4(result.worldTransform), sc);
+ [self.sceneView.scene.rootNode addChildNode:model];
+ }
+}
+
+- (void) startObserver
+{
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
+}
+
+- (void) stopObserver
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
+}
+
+-(void)OrientationDidChange:(NSNotification*)notification
+{
+ [self logMessage:@"adjustiong sceneView frame"];
+ self.sceneView.frame = [[UIScreen mainScreen] bounds];
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugAugmentedReality)
+ {
+ va_list args;
+ va_start(args, format);
+ NSString* formattedMessage = [[NSString alloc] initWithFormat: format arguments: args];
+ AttachLog([@"[Debug] " stringByAppendingString:formattedMessage]);
+ va_end(args);
+ [formattedMessage release];
+ }
+}
+@end
diff --git a/modules/barcode-scan/build.gradle b/modules/barcode-scan/build.gradle
index 2de54b12..c09a8af4 100644
--- a/modules/barcode-scan/build.gradle
+++ b/modules/barcode-scan/build.gradle
@@ -1,3 +1,9 @@
+apply plugin: 'org.openjfx.javafxplugin'
+
+javafx {
+ modules 'javafx.graphics'
+}
+
dependencies {
implementation project(':util')
}
diff --git a/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/BarcodeScanService.java b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/BarcodeScanService.java
index 4153cdf5..073698f9 100644
--- a/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/BarcodeScanService.java
+++ b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/BarcodeScanService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Gluon
+ * Copyright (c) 2016, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/impl/IOSBarcodeScanService.java b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/impl/IOSBarcodeScanService.java
new file mode 100644
index 00000000..58f88aa2
--- /dev/null
+++ b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/impl/IOSBarcodeScanService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.barcode.impl;
+
+import com.gluonhq.attach.barcode.BarcodeScanService;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+import java.util.Optional;
+
+/**
+ * Note: Since iOS 10, the key {@code NSCameraUsageDescription} is required in
+ * the plist file in order to use this service
+ */
+public class IOSBarcodeScanService implements BarcodeScanService {
+
+ static {
+ System.loadLibrary("BarcodeScan");
+ initBarcodeScan();
+ }
+
+ private static StringProperty result;
+
+ @Override
+ public Optional scan() {
+ return scan("", "", "");
+ }
+
+ @Override
+ public Optional scan(String title, String legend, String resultText) {
+ result = new SimpleStringProperty();
+ startBarcodeScan(title != null ? title : "", legend != null ? legend : "", resultText != null ? resultText : "");
+ try {
+ Platform.enterNestedEventLoop(result);
+ } catch (Exception e) {
+ System.out.println("ScanActivity: enterNestedEventLoop failed: " + e);
+ }
+ return Optional.ofNullable(result.get());
+ }
+
+ // callback
+
+ public static void setResult(String v) {
+ result.set(v);
+ Platform.runLater(() -> {
+ try {
+ Platform.exitNestedEventLoop(result, null);
+ } catch (Exception e) {
+ System.out.println("ScanActivity: exitNestedEventLoop failed: " + e);
+ }
+ });
+ }
+
+ // native
+
+ private static native void initBarcodeScan(); // init IDs for java callbacks from native
+
+ // scanning service
+ private static native void startBarcodeScan(String title, String legend, String resultText);
+
+}
diff --git a/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/package-info.java b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/package-info.java
index 4509922c..41b6cb33 100644
--- a/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/package-info.java
+++ b/modules/barcode-scan/src/main/java/com/gluonhq/attach/barcode/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - BarcodeScan plugin,
+ * Primary API package for Attach - BarcodeScan plugin,
* contains the interface {@link com.gluonhq.attach.barcode.BarcodeScanService} and related classes.
*/
package com.gluonhq.attach.barcode;
\ No newline at end of file
diff --git a/modules/barcode-scan/src/main/java/module-info.java b/modules/barcode-scan/src/main/java/module-info.java
index 0308f428..20b78a26 100644
--- a/modules/barcode-scan/src/main/java/module-info.java
+++ b/modules/barcode-scan/src/main/java/module-info.java
@@ -27,6 +27,7 @@
*/
module com.gluonhq.attach.barcode {
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
exports com.gluonhq.attach.barcode;
diff --git a/modules/barcode-scan/src/main/native/ios/BarcodeScan.h b/modules/barcode-scan/src/main/native/ios/BarcodeScan.h
new file mode 100644
index 00000000..ca290bff
--- /dev/null
+++ b/modules/barcode-scan/src/main/native/ios/BarcodeScan.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface BarcodeScan : UIViewController {}
+ - (void) display:(NSString *)title legend:(NSString *)legend resultText:(NSString *)resultText;
+@end
+
+void sendScanResult(NSString *scanResult);
diff --git a/modules/barcode-scan/src/main/native/ios/BarcodeScan.m b/modules/barcode-scan/src/main/native/ios/BarcodeScan.m
new file mode 100644
index 00000000..a47a331e
--- /dev/null
+++ b/modules/barcode-scan/src/main/native/ios/BarcodeScan.m
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "BarcodeScan.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_BarcodeScan(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int BarcodeScanInited = 0;
+
+// BarcodeScan
+jclass mat_jScanServiceClass;
+jmethodID mat_jScanService_setResult = 0;
+BarcodeScan *_barcodeScan;
+
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_barcode_impl_IOSBarcodeScanService_initBarcodeScan
+(JNIEnv *env, jclass jClass)
+{
+ if (BarcodeScanInited)
+ {
+ return;
+ }
+ BarcodeScanInited = 1;
+
+ mat_jScanServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/barcode/impl/IOSBarcodeScanService"));
+ mat_jScanService_setResult = (*env)->GetStaticMethodID(env, mat_jScanServiceClass, "setResult", "(Ljava/lang/String;)V");
+}
+
+void sendScanResult(NSString *scanResult) {
+ const char *scanChars = [scanResult UTF8String];
+ jstring arg = (*env)->NewStringUTF(env, scanChars);
+ (*env)->CallStaticVoidMethod(env, mat_jScanServiceClass, mat_jScanService_setResult, arg);
+ (*env)->DeleteLocalRef(env, arg);
+ AttachLog(@"Finished sending scan result");
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_barcode_impl_IOSBarcodeScanService_startBarcodeScan
+(JNIEnv *env, jclass jClass, jstring jTitle, jstring jLegend, jstring jResult)
+{
+
+ const jchar *charsTitle = (*env)->GetStringChars(env, jTitle, NULL);
+ NSString *sTitle = [NSString stringWithCharacters:(UniChar *)charsTitle length:(*env)->GetStringLength(env, jTitle)];
+ (*env)->ReleaseStringChars(env, jTitle, charsTitle);
+
+ const jchar *charsLegend = (*env)->GetStringChars(env, jLegend, NULL);
+ NSString *sLegend = [NSString stringWithCharacters:(UniChar *)charsLegend length:(*env)->GetStringLength(env, jLegend)];
+ (*env)->ReleaseStringChars(env, jLegend, charsLegend);
+
+ const jchar *charsResult = (*env)->GetStringChars(env, jResult, NULL);
+ NSString *sResult = [NSString stringWithCharacters:(UniChar *)charsResult length:(*env)->GetStringLength(env, jResult)];
+ (*env)->ReleaseStringChars(env, jResult, charsResult);
+
+ _barcodeScan = [[BarcodeScan alloc] init];
+ [_barcodeScan display:sTitle legend:sLegend resultText:sResult];
+ return;
+}
+
+@implementation BarcodeScan
+
+AVCaptureSession *_session;
+AVCaptureDevice *_device;
+AVCaptureDeviceInput *_input;
+AVCaptureMetadataOutput *_output;
+AVCaptureVideoPreviewLayer *_prevLayer;
+UINavigationItem *currentItem;
+UINavigationBar *navBar;
+NSString *resultString;
+
+- (void)display:(NSString *)title legend:(NSString *)legend resultText:(NSString *)resultText
+{
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+
+ // get the root view controller
+ UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
+ if(!rootViewController)
+ {
+ AttachLog(@"rootViewController was nil");
+ return;
+ }
+
+ resultString = resultText;
+
+ // get the view
+ UIView *view = self.view;
+
+ _session = [[AVCaptureSession alloc] init];
+ _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+ NSError *error = nil;
+
+ _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:&error];
+ if (_input) {
+ [_session addInput:_input];
+ } else {
+ AttachLog(@"Error: %@", error);
+ }
+
+ _output = [[AVCaptureMetadataOutput alloc] init];
+ [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
+ [_session addOutput:_output];
+
+ _output.metadataObjectTypes = [_output availableMetadataObjectTypes];
+
+ _prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
+ _prevLayer.frame = view.bounds;
+ _prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
+ _prevLayer.connection.videoOrientation = [self videoOrientationFromCurrentDeviceOrientation];
+ [view.layer addSublayer:_prevLayer];
+
+ CGRect sbFrame = [[UIApplication sharedApplication] statusBarFrame];
+ int ofs = sbFrame.size.height;
+
+ navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, ofs, self.view.frame.size.width, 44)];
+ [navBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
+ navBar.shadowImage = [UIImage new];
+ navBar.translucent = YES;
+ [navBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}];
+
+ currentItem = [[UINavigationItem alloc] init];
+ if ([title length] != 0) {
+ currentItem.title = title;
+ }
+
+ UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain
+ target:self action:@selector(cancel:)];
+ currentItem.leftBarButtonItem = leftButton;
+
+ navBar.items = @[ currentItem ];
+ [view addSubview:navBar];
+
+ // show view controller
+ [rootViewController presentViewController:self animated:YES completion:nil];
+ [_session startRunning];
+
+ if ([legend length] != 0) {
+ UIAlertController *toast = [UIAlertController alertControllerWithTitle:nil message:legend preferredStyle:UIAlertControllerStyleAlert];
+ [self presentViewController:toast animated:YES completion:nil];
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ [NSThread sleepForTimeInterval:2.0f];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [toast dismissViewControllerAnimated:YES completion:nil];
+ });
+ });
+ }
+}
+
+// hide barcodeScan preview and view controller
+- (IBAction)cancel:(id)sender
+{
+ AttachLog(@"Scan cancelled");
+ NSString *result = nil;
+ sendScanResult(result);
+ [self end];
+}
+
+- (void)end
+{
+ if([_session isRunning])
+ {
+ [_session stopRunning];
+ }
+ [_session removeInput:_input];
+ [_session removeOutput:_output];
+ [_prevLayer removeFromSuperlayer];
+ [currentItem release];
+ currentItem = nil;
+ [navBar removeFromSuperview];
+ [navBar release];
+ navBar = nil;
+ [self dismissViewControllerAnimated:YES completion:nil];
+ _prevLayer = nil;
+ _session = nil;
+ resultString = nil;
+}
+
+// device will / did rotate
+- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator
+{
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+ // Place code here which is performed before the rotation animation starts.
+
+ [coordinator animateAlongsideTransition:^(id context)
+ {
+ // Perform this code here during rotation animation
+
+ } completion:^(id context)
+ {
+
+ // rotation finished, resize preview layer
+ _prevLayer.frame = self.view.bounds;
+ // rotate camera based on new orientation
+ _prevLayer.connection.videoOrientation = [self videoOrientationFromCurrentDeviceOrientation];
+
+ CGRect sbFrame = [[UIApplication sharedApplication] statusBarFrame];
+ int ofs = sbFrame.size.height;
+ navBar.frame = CGRectMake(0, ofs, self.view.frame.size.width, 44);
+
+ }];
+}
+
+- (AVCaptureVideoOrientation) videoOrientationFromCurrentDeviceOrientation {
+ UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
+
+ if (orientation == UIDeviceOrientationPortraitUpsideDown)
+ return AVCaptureVideoOrientationPortraitUpsideDown;
+ else if(orientation == UIInterfaceOrientationPortrait)
+ return AVCaptureVideoOrientationPortrait;
+ else if(orientation == UIInterfaceOrientationLandscapeLeft)
+ return AVCaptureVideoOrientationLandscapeLeft;
+ else if(orientation == UIInterfaceOrientationLandscapeRight)
+ return AVCaptureVideoOrientationLandscapeRight;
+
+ return AVCaptureVideoOrientationPortrait;
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
+{
+ NSString *detectionString = nil;
+ NSArray *barCodeTypes = @[AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code,
+ AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code,
+ AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode];
+
+ for (AVMetadataObject *metadata in metadataObjects) {
+ for (NSString *type in barCodeTypes) {
+ if ([metadata.type isEqualToString:type])
+ {
+ detectionString = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
+ break;
+ }
+ }
+
+ if (detectionString != nil)
+ {
+ break;
+ }
+ else
+ {
+ AttachLog(@"String: none");
+ NSString *result = nil;
+ sendScanResult(result);
+ }
+ }
+
+ if (detectionString != nil)
+ {
+ AttachLog(@"String: %@", detectionString);
+ if ([resultString length] != 0) {
+ if([_session isRunning])
+ {
+ [_session stopRunning];
+ }
+ [_session removeInput:_input];
+ [_session removeOutput:_output];
+ UIAlertController *toast =[UIAlertController alertControllerWithTitle:nil
+ message:[NSString stringWithFormat:@"%@: %@",resultString, detectionString]
+ preferredStyle:UIAlertControllerStyleAlert];
+ [self presentViewController:toast animated:YES completion:nil];
+
+ int duration = 2; // in seconds
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [toast dismissViewControllerAnimated:YES completion:^{
+ sendScanResult(detectionString);
+ [self end];
+ }];
+ });
+ } else {
+ sendScanResult(detectionString);
+ [self end];
+ }
+ }
+
+}
+@end
diff --git a/modules/battery/build.gradle b/modules/battery/build.gradle
index a69d920d..fe457a59 100644
--- a/modules/battery/build.gradle
+++ b/modules/battery/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(":util")
+ implementation project(":lifecycle")
}
ext.description = 'Common API to access battery features'
\ No newline at end of file
diff --git a/modules/battery/src/main/java/com/gluonhq/attach/battery/BatteryService.java b/modules/battery/src/main/java/com/gluonhq/attach/battery/BatteryService.java
index 0c0bebd5..0c614f07 100644
--- a/modules/battery/src/main/java/com/gluonhq/attach/battery/BatteryService.java
+++ b/modules/battery/src/main/java/com/gluonhq/attach/battery/BatteryService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Gluon
+ * Copyright (c) 2016, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/DummyBatteryService.java b/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/DummyBatteryService.java
index 1359b7a0..d476732a 100644
--- a/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/DummyBatteryService.java
+++ b/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/DummyBatteryService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.battery.impl;
import com.gluonhq.attach.battery.BatteryService;
diff --git a/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/IOSBatteryService.java b/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/IOSBatteryService.java
new file mode 100644
index 00000000..20316287
--- /dev/null
+++ b/modules/battery/src/main/java/com/gluonhq/attach/battery/impl/IOSBatteryService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.battery.impl;
+
+import com.gluonhq.attach.battery.BatteryService;
+import com.gluonhq.attach.lifecycle.LifecycleEvent;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.beans.property.ReadOnlyFloatProperty;
+import javafx.beans.property.ReadOnlyFloatWrapper;
+
+public class IOSBatteryService implements BatteryService {
+
+ static {
+ System.loadLibrary("Battery");
+ initBattery();
+ }
+
+ private static final ReadOnlyBooleanWrapper PLUGGED_IN = new ReadOnlyBooleanWrapper();
+ private static final ReadOnlyFloatWrapper BATTERY_LEVEL = new ReadOnlyFloatWrapper();
+
+ public IOSBatteryService() {
+ LifecycleService.create().ifPresent(l -> {
+ l.addListener(LifecycleEvent.PAUSE, IOSBatteryService::stopObserver);
+ l.addListener(LifecycleEvent.RESUME, IOSBatteryService::startObserver);
+ });
+ startObserver();
+ }
+
+ @Override
+ public float getBatteryLevel() {
+ return BATTERY_LEVEL.get();
+ }
+
+ @Override
+ public ReadOnlyFloatProperty batteryLevelProperty() {
+ return BATTERY_LEVEL.getReadOnlyProperty();
+ }
+
+ @Override
+ public boolean isPluggedIn() {
+ return PLUGGED_IN.get();
+ }
+
+ @Override
+ public ReadOnlyBooleanProperty pluggedInProperty() {
+ return PLUGGED_IN.getReadOnlyProperty();
+ }
+
+ // native
+ private static native void initBattery();
+ private static native void startObserver();
+ private static native void stopObserver();
+
+ // callback
+ private static void notifyBatteryState(String state) {
+ if (state == null) {
+ return;
+ }
+ // ios docs: charging -> device is plugged into power and the battery is less than 100% charged
+ // or full -> device is plugged into power and the battery is 100% charged
+ boolean plugged = state.equals("Charging") || state.equals("Full");
+ if (PLUGGED_IN != null && PLUGGED_IN.get() != plugged) {
+ Platform.runLater(() -> PLUGGED_IN.set(plugged));
+ }
+ }
+ private static void notifyBatteryLevel(float level) {
+ if (BATTERY_LEVEL != null && BATTERY_LEVEL.get() != level) {
+ Platform.runLater(() -> BATTERY_LEVEL.set(level));
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/battery/src/main/java/com/gluonhq/attach/battery/package-info.java b/modules/battery/src/main/java/com/gluonhq/attach/battery/package-info.java
index 410b60ff..360a40dd 100644
--- a/modules/battery/src/main/java/com/gluonhq/attach/battery/package-info.java
+++ b/modules/battery/src/main/java/com/gluonhq/attach/battery/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Battery plugin,
+ * Primary API package for Attach - Battery plugin,
* contains the interface {@link com.gluonhq.attach.battery.BatteryService} and related classes.
*/
package com.gluonhq.attach.battery;
\ No newline at end of file
diff --git a/modules/battery/src/main/java/module-info.java b/modules/battery/src/main/java/module-info.java
index 8c2e67d7..f8021136 100644
--- a/modules/battery/src/main/java/module-info.java
+++ b/modules/battery/src/main/java/module-info.java
@@ -27,8 +27,9 @@
*/
module com.gluonhq.attach.battery {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.lifecycle;
exports com.gluonhq.attach.battery;
exports com.gluonhq.attach.battery.impl to com.gluonhq.attach.util;
diff --git a/modules/battery/src/main/native/ios/Battery.h b/modules/battery/src/main/native/ios/Battery.h
new file mode 100644
index 00000000..920fc073
--- /dev/null
+++ b/modules/battery/src/main/native/ios/Battery.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+@interface Battery : NSObject {}
+ - (void) startObserver;
+ - (void) stopObserver;
+ - (NSString*) getBatteryState;
+@end
+
+void sendBatteryState();
+void sendBatteryLevel();
diff --git a/modules/battery/src/main/native/ios/Battery.m b/modules/battery/src/main/native/ios/Battery.m
new file mode 100644
index 00000000..0fb71242
--- /dev/null
+++ b/modules/battery/src/main/native/ios/Battery.m
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Battery.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Battery(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int BatteryInited = 0;
+
+// Battery
+jclass mat_jBatteryServiceClass;
+jmethodID mat_jBatteryService_notifyBatteryLevel = 0;
+jmethodID mat_jBatteryService_notifyBatteryState = 0;
+Battery *_battery;
+
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_battery_impl_IOSBatteryService_initBattery
+(JNIEnv *env, jclass jClass)
+{
+ if (BatteryInited)
+ {
+ return;
+ }
+ BatteryInited = 1;
+
+ mat_jBatteryServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/battery/impl/IOSBatteryService"));
+ mat_jBatteryService_notifyBatteryLevel = (*env)->GetStaticMethodID(env, mat_jBatteryServiceClass, "notifyBatteryLevel", "(F)V");
+ mat_jBatteryService_notifyBatteryState = (*env)->GetStaticMethodID(env, mat_jBatteryServiceClass, "notifyBatteryState", "(Ljava/lang/String;)V");
+
+ _battery = [[Battery alloc] init];
+
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_battery_impl_IOSBatteryService_startObserver
+(JNIEnv *env, jclass jClass)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_battery startObserver];
+ });
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_battery_impl_IOSBatteryService_stopObserver
+(JNIEnv *env, jclass jClass)
+{
+ [_battery stopObserver];
+ return;
+}
+
+void sendBatteryState() {
+ NSString *battery = [_battery getBatteryState];
+ const char *batteryChars = [battery UTF8String];
+ jstring arg = (*env)->NewStringUTF(env, batteryChars);
+
+ (*env)->CallStaticVoidMethod(env, mat_jBatteryServiceClass, mat_jBatteryService_notifyBatteryState, arg);
+ (*env)->DeleteLocalRef(env, arg);
+}
+
+void sendBatteryLevel() {
+ float batteryLevel = [[UIDevice currentDevice] batteryLevel];
+ (*env)->CallStaticVoidMethod(env, mat_jBatteryServiceClass, mat_jBatteryService_notifyBatteryLevel, batteryLevel);
+}
+
+@implementation Battery
+
+- (void) startObserver
+{
+ [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(BatteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(BatteryLevelDidChange:) name:UIDeviceBatteryLevelDidChangeNotification object:nil];
+ sendBatteryState();
+ sendBatteryLevel();
+}
+
+- (void) stopObserver
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryStateDidChangeNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceBatteryLevelDidChangeNotification object:nil];
+ [[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];
+}
+
+- (NSString*) getBatteryState
+{
+ int state = [[UIDevice currentDevice] batteryState];
+
+ NSMutableString *value;
+ if(state == UIDeviceBatteryStateUnknown)
+ value = [NSMutableString stringWithString: @"Unknown"];
+ else if(state == UIDeviceBatteryStateUnplugged)
+ value = [NSMutableString stringWithString: @"Unplugged"];
+ else if(state == UIDeviceBatteryStateCharging)
+ value = [NSMutableString stringWithString: @"Charging"];
+ else if(state == UIDeviceBatteryStateFull)
+ value = [NSMutableString stringWithString: @"Full"];
+ else
+ value = [NSMutableString stringWithString: @"Unknown"];
+ return value;
+}
+
+-(void)BatteryStateDidChange:(NSNotification*)notification
+{
+ sendBatteryState();
+}
+
+-(void)BatteryLevelDidChange:(NSNotification*)notification
+{
+ sendBatteryLevel();
+}
+
+@end
+
diff --git a/modules/ble/build.gradle b/modules/ble/build.gradle
index 0e832568..34928e10 100644
--- a/modules/ble/build.gradle
+++ b/modules/ble/build.gradle
@@ -1,3 +1,9 @@
+apply plugin: 'org.openjfx.javafxplugin'
+
+javafx {
+ modules 'javafx.graphics'
+}
+
dependencies {
implementation project(':util')
}
diff --git a/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/DummyBleService.java b/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/DummyBleService.java
index 5d2d93e6..086e0e13 100644
--- a/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/DummyBleService.java
+++ b/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/DummyBleService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.ble.impl;
import com.gluonhq.attach.ble.BleService;
diff --git a/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/IOSBleService.java b/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/IOSBleService.java
new file mode 100644
index 00000000..9d9e7567
--- /dev/null
+++ b/modules/ble/src/main/java/com/gluonhq/attach/ble/impl/IOSBleService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.ble.impl;
+
+import com.gluonhq.attach.ble.BleService;
+import com.gluonhq.attach.ble.Configuration;
+import com.gluonhq.attach.ble.ScanDetection;
+import javafx.application.Platform;
+
+import java.util.function.Consumer;
+
+/**
+ * iOS implementation of BleService
+ *
+ * Important note: it requires adding to the info.plist file:
+ *
+ * {@code
+ * NSLocationUsageDescription
+ * Reason to use Location Service (iOS 6+)
+ * NSLocationAlwaysUsageDescription
+ * Reason to use Location Service (iOS 8+)
+ * }
+*/
+public class IOSBleService implements BleService {
+
+ static {
+ System.loadLibrary("Ble");
+ initBle();
+ }
+
+ private static Consumer callback;
+
+ /**
+ * startScanning is called with a given uuid and a callback for the beacon found.
+ * iOS iBeacon only scans for given uuid's
+ *
+ * iOS apps using BleService require the use of the key
+ * NSLocationAlwaysDescription in the plist file so the user is
+ * asked about allowing location services
+ *
+ * @param region Containing the beacon uuid
+ * @param callback Callback added to the beacon
+ */
+
+ @Override
+ public void startScanning(Configuration region, Consumer callback) {
+ this.callback = callback;
+ String[] uuids = new String[region.getUuids().size()];
+ uuids = region.getUuids().toArray(uuids);
+ startObserver(uuids);
+ }
+
+ /**
+ * stopScanning, if the manager is initialized
+ */
+ @Override
+ public void stopScanning() {
+ stopObserver();
+ }
+
+
+ // native
+ private static native void initBle(); // init IDs for java callbacks from native
+ private static native void startObserver(String[] uuids);
+ private static native void stopObserver();
+
+ // callback
+ private static void setDetection(String uuid, int major, int minor, int rssi, int proximity) {
+ ScanDetection detection = new ScanDetection();
+ detection.setUuid(uuid);
+ detection.setMajor(major);
+ detection.setMinor(minor);
+ detection.setRssi(rssi);
+ detection.setProximity(proximity);
+ Platform.runLater(() -> callback.accept(detection));
+ }
+}
diff --git a/modules/ble/src/main/java/module-info.java b/modules/ble/src/main/java/module-info.java
index 25a56c93..3d46b031 100644
--- a/modules/ble/src/main/java/module-info.java
+++ b/modules/ble/src/main/java/module-info.java
@@ -27,6 +27,7 @@
*/
module com.gluonhq.attach.ble {
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
exports com.gluonhq.attach.ble;
diff --git a/modules/ble/src/main/native/ios/BLE.h b/modules/ble/src/main/native/ios/BLE.h
new file mode 100644
index 00000000..33d89ee7
--- /dev/null
+++ b/modules/ble/src/main/native/ios/BLE.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+#import
+
+@interface Ble : UIViewController
+{
+}
+ @property(nonatomic, strong) CLLocationManager *locationManager;
+ @property(nonatomic, strong) CBCentralManager *bluetoothManager;
+ - (void) startObserver;
+ - (void) stopObserver;
+@end
+
+void setDetection(CLBeacon *foundBeacon);
\ No newline at end of file
diff --git a/modules/ble/src/main/native/ios/BLE.m b/modules/ble/src/main/native/ios/BLE.m
new file mode 100644
index 00000000..f207f782
--- /dev/null
+++ b/modules/ble/src/main/native/ios/BLE.m
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "BLE.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Ble(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int BleInited = 0;
+
+// Ble
+jclass mat_jBleServiceClass;
+jmethodID mat_jBleService_setDetection = 0;
+Ble *_Ble;
+NSArray *arrayOfUuids;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ble_impl_IOSBleService_initBle
+(JNIEnv *env, jclass jClass)
+{
+ if (BleInited)
+ {
+ return;
+ }
+ BleInited = 1;
+
+ mat_jBleServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/ble/impl/IOSBleService"));
+ mat_jBleService_setDetection = (*env)->GetStaticMethodID(env, mat_jBleServiceClass, "setDetection", "(Ljava/lang/String;IIII)V");
+
+ _Ble = [[Ble alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ble_impl_IOSBleService_startObserver
+(JNIEnv *env, jclass jClass, jobjectArray jUuidsArray)
+{
+ int uuidCount = (*env)->GetArrayLength(env, jUuidsArray);
+ NSMutableArray *uuids = [[NSMutableArray alloc] init];
+
+ for (int i=0; iGetObjectArrayElement(env, jUuidsArray, i));
+ const jchar *uuidString = (*env)->GetStringChars(env, juuid, NULL);
+ NSString *uuid = [NSString stringWithCharacters:(UniChar *)uuidString length:(*env)->GetStringLength(env, juuid)];
+ (*env)->ReleaseStringChars(env, juuid, uuidString);
+ [uuids addObject:uuid];
+ }
+ arrayOfUuids = [NSArray arrayWithArray:uuids];
+
+ [_Ble startObserver];
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_ble_impl_IOSBleService_stopObserver
+(JNIEnv *env, jclass jClass)
+{
+ [_Ble stopObserver];
+ return;
+}
+
+void setDetection(CLBeacon *foundBeacon) {
+ if (foundBeacon)
+ {
+ NSString *uuid = foundBeacon.proximityUUID.UUIDString;
+ int major = [foundBeacon.major intValue];
+ int minor = [foundBeacon.minor intValue];
+ int rssi = foundBeacon.rssi;
+ int proximity = 0;
+ switch (foundBeacon.proximity)
+ {
+ case CLProximityUnknown: { proximity = 0; } break;
+ case CLProximityImmediate: { proximity = 1; } break;
+ case CLProximityNear: { proximity = 2; } break;
+ case CLProximityFar: { proximity = 3; } break;
+ default: { proximity = 0; } break;
+ }
+ const char *uuidChars = [uuid UTF8String];
+ jstring juuid = (*env)->NewStringUTF(env, uuidChars);
+ (*env)->CallStaticVoidMethod(env, mat_jBleServiceClass, mat_jBleService_setDetection, juuid, major, minor, rssi, proximity);
+ (*env)->DeleteLocalRef(env, juuid);
+ }
+}
+
+@implementation Ble
+
+- (void)startObserver
+{
+ if (!self.locationManager) {
+ self.locationManager = [[CLLocationManager alloc] init];
+ self.locationManager.delegate = self;
+
+ _bluetoothManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
+ }
+
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ {
+ // Requires Always. With WhenInUse it won't work
+ [self.locationManager requestAlwaysAuthorization];
+ }
+
+ AttachLog(@"Start monitoring for regions");
+ for (NSString* uuidString in arrayOfUuids)
+ {
+ NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
+ CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
+ identifier:[NSString stringWithFormat:@"com.gluonhq.beacon.%@", uuidString]];
+ [self.locationManager startMonitoringForRegion:beaconRegion];
+ [self.locationManager startRangingBeaconsInRegion:beaconRegion];
+ }
+}
+
+- (void)stopObserver
+{
+ AttachLog(@"Stop monitoring for regions");
+ if (self.locationManager)
+ {
+ NSSet *setOfRegions = [self.locationManager monitoredRegions];
+ for (CLRegion *region in setOfRegions) {
+ [self.locationManager stopMonitoringForRegion:(CLBeaconRegion *) region];
+ [self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *) region];
+ }
+ }
+}
+
+- (void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion*)region
+{
+ [self.locationManager startRangingBeaconsInRegion:(CLBeaconRegion *) region];
+}
+
+-(void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion*)region
+{
+ [self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *) region];
+}
+
+- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region withError:(NSError *)error
+{
+ AttachLog(@"Ranging Beacons failed with error: %@", [error localizedDescription]);
+}
+
+-(void)locationManager:(CLLocationManager*)manager didRangeBeacons:(NSArray*)beacons inRegion:(CLBeaconRegion*)region
+{
+ // sorted by proximity
+ CLBeacon *foundBeacon = [beacons firstObject];
+ setDetection(foundBeacon);
+}
+
+- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
+{
+ AttachLog(@"Region monitoring failed with error: %@", [error localizedDescription]);
+}
+
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
+{
+ NSString *stateString = nil;
+ if (@available(iOS 10.0, *))
+ {
+ switch(_bluetoothManager.state)
+ {
+ case CBManagerStateResetting: stateString = @"The connection with the system service was momentarily lost, update imminent."; break;
+ case CBManagerStateUnsupported: stateString = @"The platform doesn't support Bluetooth Low Energy."; break;
+ case CBManagerStateUnauthorized: stateString = @"The app is not authorized to use Bluetooth Low Energy."; break;
+ case CBManagerStatePoweredOff: stateString = @"Bluetooth is currently powered off."; break;
+ case CBManagerStatePoweredOn: stateString = @"Bluetooth is currently powered on and available to use."; break;
+ default: stateString = @"State unknown, update imminent."; break;
+ }
+ }
+ else
+ {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ switch(_bluetoothManager.state)
+ {
+ case CBCentralManagerStateResetting: stateString = @"The connection with the system service was momentarily lost, update imminent."; break;
+ case CBCentralManagerStateUnsupported: stateString = @"The platform doesn't support Bluetooth Low Energy."; break;
+ case CBCentralManagerStateUnauthorized: stateString = @"The app is not authorized to use Bluetooth Low Energy."; break;
+ case CBCentralManagerStatePoweredOff: stateString = @"Bluetooth is currently powered off."; break;
+ case CBCentralManagerStatePoweredOn: stateString = @"Bluetooth is currently powered on and available to use."; break;
+ default: stateString = @"State unknown, update imminent."; break;
+ }
+
+ #pragma clang diagnostic pop
+ }
+ AttachLog(@"Bluetooth State: %@",stateString);
+}
+
+@end
diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java
index c228f635..23d3bcb2 100644
--- a/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java
+++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/BrowserService.java
@@ -62,8 +62,8 @@ static Optional create() {
* Launches the user-default browser to show a specified URL.
*
* @param url The URL to load when the browser application opens.
- * @throws java.io.IOException
- * @throws java.net.URISyntaxException
+ * @throws java.io.IOException If the URL can't be opened
+ * @throws java.net.URISyntaxException If it is not a valid URL string
*/
void launchExternalBrowser(String url) throws IOException, URISyntaxException;
}
diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DummyBrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DummyBrowserService.java
index d7425e7b..adf4f877 100644
--- a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DummyBrowserService.java
+++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/DummyBrowserService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.browser.impl;
import com.gluonhq.attach.browser.BrowserService;
diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java
new file mode 100644
index 00000000..aa66132c
--- /dev/null
+++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/impl/IOSBrowserService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.browser.impl;
+
+
+import com.gluonhq.attach.browser.BrowserService;
+
+import java.io.IOException;
+
+public class IOSBrowserService implements BrowserService {
+
+ static {
+ System.loadLibrary("Browser");
+ }
+
+ @Override
+ public void launchExternalBrowser(String url) throws IOException {
+ if (!launchURL(url)) {
+ throw new IOException("Error launching url " + url);
+ }
+ }
+
+ private native boolean launchURL(String url);
+
+}
diff --git a/modules/browser/src/main/java/com/gluonhq/attach/browser/package-info.java b/modules/browser/src/main/java/com/gluonhq/attach/browser/package-info.java
index a3253884..90be6d59 100644
--- a/modules/browser/src/main/java/com/gluonhq/attach/browser/package-info.java
+++ b/modules/browser/src/main/java/com/gluonhq/attach/browser/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Browser plugin,
+ * Primary API package for Attach - Browser plugin,
* contains the interface {@link com.gluonhq.attach.browser.BrowserService} and related classes.
*/
package com.gluonhq.attach.browser;
\ No newline at end of file
diff --git a/modules/browser/src/main/native/ios/Browser.m b/modules/browser/src/main/native/ios/Browser.m
new file mode 100644
index 00000000..67f31fa6
--- /dev/null
+++ b/modules/browser/src/main/native/ios/Browser.m
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Browser(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_browser_impl_IOSBrowserService_launchURL
+(JNIEnv *env, jclass jClass, jstring jUrl)
+{
+ const jchar *chars = (*env)->GetStringChars(env, jUrl, NULL);
+ NSString *url = [NSString stringWithCharacters:(UniChar *)chars length:(*env)->GetStringLength(env, jUrl)];
+ (*env)->ReleaseStringChars(env, jUrl, chars);
+
+ NSURL *nsUrl = [NSURL URLWithString:url];
+ if ([[UIApplication sharedApplication] canOpenURL:nsUrl]) {
+ if (@available(iOS 10.0, *))
+ {
+ [[UIApplication sharedApplication] openURL:nsUrl options:@{}
+ completionHandler:^(BOOL success) {
+ if (success) {
+ AttachLog(@"Opened url successfully");
+ }
+ }];
+ }
+ else {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ [[UIApplication sharedApplication] openURL:nsUrl];
+
+ #pragma clang diagnostic pop
+ }
+ AttachLog(@"Done opening url %@", url);
+ return JNI_TRUE;
+ } else {
+ AttachLog(@"Can't open url %@", url);
+ return JNI_FALSE;
+ }
+}
+
diff --git a/modules/cache/src/main/java/com/gluonhq/attach/cache/package-info.java b/modules/cache/src/main/java/com/gluonhq/attach/cache/package-info.java
index be2c0780..be1fdf59 100644
--- a/modules/cache/src/main/java/com/gluonhq/attach/cache/package-info.java
+++ b/modules/cache/src/main/java/com/gluonhq/attach/cache/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2019 Gluon
+ * Copyright (c) 2016, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Cache plugin,
+ * Primary API package for Attach - Cache plugin,
* contains the interface {@link com.gluonhq.attach.cache.CacheService} and related classes.
*/
package com.gluonhq.attach.cache;
\ No newline at end of file
diff --git a/modules/compass/build.gradle b/modules/compass/build.gradle
index 80625449..efd45399 100644
--- a/modules/compass/build.gradle
+++ b/modules/compass/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(":util")
+ implementation project(":magnetometer")
}
ext.description = 'Common API to access compass features'
\ No newline at end of file
diff --git a/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/DummyCompassService.java b/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/DummyCompassService.java
index 3cd07999..85f46ea6 100644
--- a/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/DummyCompassService.java
+++ b/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/DummyCompassService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.compass.impl;
import com.gluonhq.attach.compass.CompassService;
diff --git a/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/IOSCompassService.java b/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/IOSCompassService.java
new file mode 100644
index 00000000..3af77da5
--- /dev/null
+++ b/modules/compass/src/main/java/com/gluonhq/attach/compass/impl/IOSCompassService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.compass.impl;
+
+import com.gluonhq.attach.compass.CompassService;
+import com.gluonhq.attach.magnetometer.MagnetometerService;
+import com.gluonhq.attach.util.Services;
+import javafx.beans.property.ReadOnlyDoubleProperty;
+import javafx.beans.property.ReadOnlyDoubleWrapper;
+
+public class IOSCompassService implements CompassService {
+
+ private final ReadOnlyDoubleWrapper heading;
+
+ public IOSCompassService() {
+ heading = new ReadOnlyDoubleWrapper();
+
+ Services.get(MagnetometerService.class).ifPresent(m -> {
+ m.readingProperty().addListener((obs, ov, nv) -> heading.setValue(nv.getAzimuth()));
+ });
+ }
+
+ @Override
+ public double getHeading() {
+ return heading.get();
+ }
+
+ @Override
+ public ReadOnlyDoubleProperty headingProperty() {
+ return heading.getReadOnlyProperty();
+ }
+
+}
diff --git a/modules/compass/src/main/java/com/gluonhq/attach/compass/package-info.java b/modules/compass/src/main/java/com/gluonhq/attach/compass/package-info.java
index 62acb789..d6e8e8b7 100644
--- a/modules/compass/src/main/java/com/gluonhq/attach/compass/package-info.java
+++ b/modules/compass/src/main/java/com/gluonhq/attach/compass/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2019 Gluon
+ * Copyright (c) 2016, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Compass plugin,
+ * Primary API package for Attach - Compass plugin,
* contains the interface {@link com.gluonhq.attach.compass.CompassService} and related classes.
*/
package com.gluonhq.attach.compass;
\ No newline at end of file
diff --git a/modules/compass/src/main/java/module-info.java b/modules/compass/src/main/java/module-info.java
index 32b7c10d..c611c9f3 100644
--- a/modules/compass/src/main/java/module-info.java
+++ b/modules/compass/src/main/java/module-info.java
@@ -27,8 +27,9 @@
*/
module com.gluonhq.attach.compass {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.magnetometer;
exports com.gluonhq.attach.compass;
exports com.gluonhq.attach.compass.impl to com.gluonhq.attach.util;
diff --git a/modules/connectivity/build.gradle b/modules/connectivity/build.gradle
index bc8f221d..dc077cd8 100644
--- a/modules/connectivity/build.gradle
+++ b/modules/connectivity/build.gradle
@@ -1,7 +1,7 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
diff --git a/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/DummyConnectivityService.java b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/DummyConnectivityService.java
index e91e6e22..4bf453f1 100644
--- a/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/DummyConnectivityService.java
+++ b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/DummyConnectivityService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.connectivity.impl;
import com.gluonhq.attach.connectivity.ConnectivityService;
diff --git a/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/IOSConnectivityService.java b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/IOSConnectivityService.java
new file mode 100644
index 00000000..70910bbb
--- /dev/null
+++ b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/impl/IOSConnectivityService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.connectivity.impl;
+
+import com.gluonhq.attach.connectivity.ConnectivityService;
+import com.gluonhq.attach.util.PropertyWatcher;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+
+public class IOSConnectivityService implements ConnectivityService {
+
+ static {
+ System.loadLibrary("Connectivity");
+ }
+
+ private native boolean singleCheck();
+
+ private ReadOnlyBooleanWrapper connectedProperty;
+
+ @Override
+ public ReadOnlyBooleanProperty connectedProperty() {
+ if (connectedProperty == null) {
+ connectedProperty = new ReadOnlyBooleanWrapper();
+ PropertyWatcher.addPropertyWatcher(() -> {
+ final boolean connected = isConnected();
+ if (connectedProperty.getValue() != connected) {
+ Platform.runLater(() -> connectedProperty.setValue(connected));
+ }
+ });
+ }
+ return connectedProperty.getReadOnlyProperty();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return singleCheck();
+ }
+}
\ No newline at end of file
diff --git a/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/package-info.java b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/package-info.java
index d73ea4d6..6b4b8f08 100644
--- a/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/package-info.java
+++ b/modules/connectivity/src/main/java/com/gluonhq/attach/connectivity/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Gluon
+ * Copyright (c) 2016, 2019, Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Connectivity plugin,
+ * Primary API package for Attach - Connectivity plugin,
* contains the interface {@link com.gluonhq.attach.connectivity.ConnectivityService} and related classes.
*/
package com.gluonhq.attach.connectivity;
\ No newline at end of file
diff --git a/modules/connectivity/src/main/java/module-info.java b/modules/connectivity/src/main/java/module-info.java
index 316a9c14..c39af644 100644
--- a/modules/connectivity/src/main/java/module-info.java
+++ b/modules/connectivity/src/main/java/module-info.java
@@ -27,7 +27,7 @@
*/
module com.gluonhq.attach.connectivity {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
exports com.gluonhq.attach.connectivity;
diff --git a/modules/connectivity/src/main/native/ios/Connectivity.m b/modules/connectivity/src/main/native/ios/Connectivity.m
new file mode 100644
index 00000000..c4c7f103
--- /dev/null
+++ b/modules/connectivity/src/main/native/ios/Connectivity.m
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#include
+#include
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Connectivity(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_connectivity_impl_IOSConnectivityService_singleCheck
+(JNIEnv *env, jclass jClass) {
+
+ char *hostname;
+ struct hostent *hostinfo;
+ hostname = "apple.com";
+ hostinfo = gethostbyname(hostname);
+ if (hostinfo == NULL) {
+ return JNI_FALSE;
+ }
+ else {
+ return JNI_TRUE;
+ };
+}
\ No newline at end of file
diff --git a/modules/device/src/main/java/com/gluonhq/attach/device/impl/DummyDeviceServiceImpl.java b/modules/device/src/main/java/com/gluonhq/attach/device/impl/DummyDeviceServiceImpl.java
index 53b561a4..a8e7c9ee 100644
--- a/modules/device/src/main/java/com/gluonhq/attach/device/impl/DummyDeviceServiceImpl.java
+++ b/modules/device/src/main/java/com/gluonhq/attach/device/impl/DummyDeviceServiceImpl.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.device.impl;
import com.gluonhq.attach.device.DeviceService;
diff --git a/modules/device/src/main/java/com/gluonhq/attach/device/package-info.java b/modules/device/src/main/java/com/gluonhq/attach/device/package-info.java
new file mode 100644
index 00000000..813fee50
--- /dev/null
+++ b/modules/device/src/main/java/com/gluonhq/attach/device/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Primary API package for Attach - Device plugin,
+ * contains the interface {@link com.gluonhq.attach.device.DeviceService} and related classes.
+ */
+package com.gluonhq.attach.device;
\ No newline at end of file
diff --git a/modules/device/src/main/native/ios/Device.m b/modules/device/src/main/native/ios/Device.m
index 5141aebe..1b8407b3 100644
--- a/modules/device/src/main/native/ios/Device.m
+++ b/modules/device/src/main/native/ios/Device.m
@@ -27,6 +27,7 @@
*/
#import
#include "jni.h"
+#include "AttachMacros.h"
JNIEnv *env;
diff --git a/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/DummyDialerService.java b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/DummyDialerService.java
index 985ad1cd..3b357b1b 100644
--- a/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/DummyDialerService.java
+++ b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/DummyDialerService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.dialer.impl;
import com.gluonhq.attach.dialer.DialerService;
diff --git a/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/IOSDialerService.java b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/IOSDialerService.java
new file mode 100644
index 00000000..0672c9ba
--- /dev/null
+++ b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/impl/IOSDialerService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.dialer.impl;
+
+
+import com.gluonhq.attach.dialer.DialerService;
+
+public class IOSDialerService implements DialerService {
+
+ static {
+ System.loadLibrary("Dialer");
+ }
+
+ @Override
+ public void call(String number) {
+ if (number != null && !number.isEmpty()) {
+ callNumber(number);
+ }
+ }
+
+ private native void callNumber(String number);
+
+}
diff --git a/modules/dialer/src/main/java/com/gluonhq/attach/dialer/package-info.java b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/package-info.java
index bb8a279c..a437199e 100644
--- a/modules/dialer/src/main/java/com/gluonhq/attach/dialer/package-info.java
+++ b/modules/dialer/src/main/java/com/gluonhq/attach/dialer/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Dialer plugin,
+ * Primary API package for Attach - Dialer plugin,
* contains the interface {@link com.gluonhq.attach.dialer.DialerService} and related classes.
*/
package com.gluonhq.attach.dialer;
\ No newline at end of file
diff --git a/modules/dialer/src/main/native/ios/Dialer.m b/modules/dialer/src/main/native/ios/Dialer.m
new file mode 100644
index 00000000..00190d0c
--- /dev/null
+++ b/modules/dialer/src/main/native/ios/Dialer.m
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Dialer(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_dialer_impl_IOSDialerService_callNumber
+(JNIEnv *env, jclass jClass, jstring jNumber)
+{
+ const jchar *chars = (*env)->GetStringChars(env, jNumber, NULL);
+ NSString *number = [NSString stringWithCharacters:(UniChar *)chars length:(*env)->GetStringLength(env, jNumber)];
+ (*env)->ReleaseStringChars(env, jNumber, chars);
+ NSURL *phoneUrl = [NSURL URLWithString:[NSString stringWithFormat:@"tel://%@",number]];
+ if ([[UIApplication sharedApplication] canOpenURL:phoneUrl]) {
+ if (@available(iOS 10.0, *))
+ {
+ [[UIApplication sharedApplication] openURL:phoneUrl options:@{}
+ completionHandler:nil];
+ }
+ else {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ [[UIApplication sharedApplication] openURL:phoneUrl];
+
+ #pragma clang diagnostic pop
+ }
+ AttachLog(@"Done calling to %@", number);
+ } else {
+ AttachLog(@"Can't call to %@", number);
+ }
+}
+
diff --git a/modules/display/src/main/native/ios/Display.h b/modules/display/src/main/native/ios/Display.h
index 2ccb2381..cef07fc8 100644
--- a/modules/display/src/main/native/ios/Display.h
+++ b/modules/display/src/main/native/ios/Display.h
@@ -28,6 +28,7 @@
#import
#include "jni.h"
+#include "AttachMacros.h"
@interface Display : UIViewController {}
- (void) isIPhoneX;
diff --git a/modules/display/src/main/native/ios/Display.m b/modules/display/src/main/native/ios/Display.m
index b69c7c6b..93600ef1 100644
--- a/modules/display/src/main/native/ios/Display.m
+++ b/modules/display/src/main/native/ios/Display.m
@@ -60,7 +60,7 @@
return;
}
DisplayInited = 1;
- printf("INIT\n ");
+
mat_jDisplayServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/display/impl/IOSDisplayService"));
mat_jDisplayService_notifyDisplay = (*env)->GetStaticMethodID(env, mat_jDisplayServiceClass, "notifyDisplay", "(Ljava/lang/String;)V");
@@ -146,7 +146,7 @@
void sendNotch() {
NSString *notch = [_display getNotch];
- NSLog(@"Notch is %@", notch);
+ AttachLog(@"Notch is %@", notch);
const char *notchChars = [notch UTF8String];
jstring arg = (*env)->NewStringUTF(env, notchChars);
(*env)->CallStaticVoidMethod(env, mat_jDisplayServiceClass, mat_jDisplayService_notifyDisplay, arg);
@@ -159,7 +159,9 @@ - (void) isIPhoneX
{
iPhoneX = NO;
if ([[UIDevice currentDevice].model hasPrefix:@"iPhone"] &&
- [[UIScreen mainScreen] nativeBounds].size.height == 2436)
+ ([[UIScreen mainScreen] nativeBounds].size.height == 1792 || // XR
+ [[UIScreen mainScreen] nativeBounds].size.height == 2436 || // X, XS
+ [[UIScreen mainScreen] nativeBounds].size.height == 2688)) // XS MAX
{
iPhoneX = YES;
}
diff --git a/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/DummyInAppBillingService.java b/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/DummyInAppBillingService.java
index d049fd1f..d0069745 100644
--- a/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/DummyInAppBillingService.java
+++ b/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/DummyInAppBillingService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.inappbilling.impl;
import com.gluonhq.attach.inappbilling.InAppBillingService;
diff --git a/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/IOSInAppBillingService.java b/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/IOSInAppBillingService.java
new file mode 100644
index 00000000..86980e47
--- /dev/null
+++ b/modules/in-app-billing/src/main/java/com/gluonhq/attach/inappbilling/impl/IOSInAppBillingService.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.inappbilling.impl;
+
+import com.gluonhq.attach.inappbilling.InAppBillingException;
+import com.gluonhq.attach.inappbilling.InAppBillingQueryResult;
+import com.gluonhq.attach.inappbilling.InAppBillingQueryResultListener;
+import com.gluonhq.attach.inappbilling.InAppBillingService;
+import com.gluonhq.attach.inappbilling.Product;
+import com.gluonhq.attach.inappbilling.ProductDetails;
+import com.gluonhq.attach.inappbilling.ProductOrder;
+import com.gluonhq.attach.util.Constants;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableMap;
+import javafx.concurrent.Task;
+import javafx.concurrent.Worker;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class IOSInAppBillingService implements InAppBillingService {
+
+ static {
+ System.loadLibrary("InAppBilling");
+ initInAppBilling();
+ }
+
+ private static final ReadOnlyBooleanWrapper ready = new ReadOnlyBooleanWrapper(false);
+
+ private static InAppBillingQueryResultListener queryResultListener;
+
+ private static List registeredProducts = new LinkedList<>();
+ private final List productIds = new LinkedList<>();
+ private final List subscriptionIds = new LinkedList<>();
+ private final static List detailedProducts = new LinkedList<>();
+ private static boolean supported = true;
+ private static final BooleanProperty FETCH = new SimpleBooleanProperty();
+ private static final ObservableMap MAP_ORDERS = FXCollections.observableMap(new HashMap<>());
+
+ @Override
+ public boolean isSupported() {
+ return supported;
+ }
+
+ @Override
+ public void setQueryResultListener(InAppBillingQueryResultListener listener) {
+ this.queryResultListener = listener;
+ }
+
+ @Override
+ public void setRegisteredProducts(List registeredProducts) {
+ this.registeredProducts = registeredProducts;
+ }
+
+ @Override
+ public void initialize(String androidPublicKey, List registeredProducts) {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ this.registeredProducts = registeredProducts;
+ }
+
+ @Override
+ public boolean isReady() {
+ return ready.get();
+ }
+
+ @Override
+ public ReadOnlyBooleanProperty readyProperty() {
+ return ready.getReadOnlyProperty();
+ }
+
+ @Override
+ public Worker> fetchProductDetails() {
+ if (!isReady()) {
+ return null;
+ }
+ Task> task = new Task>() {
+
+ @Override
+ protected List call() throws Exception {
+ for (Product registeredProduct : registeredProducts) {
+ switch (registeredProduct.getType()) {
+ case CONSUMABLE:
+ case NON_CONSUMABLE:
+ productIds.add(registeredProduct.getId());
+ break;
+ case FREE_SUBSCRIPTION:
+ case RENEWABLE_SUBSCRIPTION:
+ case NON_RENEWABLE_SUBSCRIPTION:
+ subscriptionIds.add(registeredProduct.getId());
+ break;
+ }
+ }
+
+ CountDownLatch latch = new CountDownLatch(1);
+
+ FETCH.addListener(new ChangeListener() {
+ @Override
+ public void changed(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+ FETCH.removeListener(this);
+ latch.countDown();
+ }
+ });
+
+ String[] ids = new String[productIds.size()];
+ fetchProducts(productIds.toArray(ids));
+
+ if (latch.await(5, TimeUnit.MINUTES)) {
+ return detailedProducts;
+ } else {
+ throw new InAppBillingException("Products fetch operation timed out.");
+ }
+ }
+ };
+ FETCH.set(false);
+ Thread thread = new Thread(task);
+ thread.start();
+ return task;
+ }
+
+ @Override
+ public Worker order(Product product) {
+ if (!isReady()) {
+ return null;
+ }
+
+ final String key = UUID.randomUUID().toString();
+ MAP_ORDERS.put(key, null);
+ Task task = new Task() {
+
+ @Override
+ protected ProductOrder call() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ MAP_ORDERS.addListener(new MapChangeListener() {
+ @Override
+ public void onChanged(MapChangeListener.Change extends String, ? extends ProductOrder> change) {
+ if (key.equals(change.getKey())) {
+ MAP_ORDERS.removeListener(this);
+ latch.countDown();
+ }
+ }
+ });
+
+ purchaseProduct(key, product.getId());
+
+ if (latch.await(15, TimeUnit.MINUTES)) {
+ ProductOrder productOrder = MAP_ORDERS.remove(key);
+ if (productOrder == null) {
+ throw new InAppBillingException("There was an error purchasing the product");
+ }
+ return productOrder;
+ } else {
+ throw new InAppBillingException("Product order operation timed out.");
+ }
+ }
+ };
+ Thread thread = new Thread(task);
+ thread.start();
+ return task;
+ }
+
+ @Override
+ public Worker finish(ProductOrder productOrder) {
+ if (!isReady()) {
+ return null;
+ }
+ Task task = new Task() {
+
+ @Override
+ protected Product call() throws Exception {
+ if (productOrder != null && productOrder.getProduct() != null) {
+ Product product = productOrder.getProduct();
+ product.getDetails().setState(ProductDetails.State.FINISHED);
+ return product;
+ }
+ return null;
+ }
+ };
+ Thread thread = new Thread(task);
+ thread.start();
+ return task;
+ }
+
+ // native
+ private static native void initInAppBilling(); // init IDs for java callbacks from native
+ private static native void fetchProducts(String[] ids);
+ private static native void purchaseProduct(String key, String id);
+ private static native void enableDebug();
+
+ // callbacks
+ private static void setInAppReady(boolean value) {
+ supported = value;
+ Platform.runLater(() -> ready.set(value));
+ }
+
+ private static void setProduct(String id, String title, String description, String price, String currency) {
+ ProductDetails details = new ProductDetails();
+ details.setTitle(title);
+ details.setDescription(description);
+ details.setPrice(price);
+ details.setCurrency(currency);
+
+ for (Product product : registeredProducts) {
+ if (product.getId().equals(id)) {
+ Product detailedProduct = new Product(product.getId(), product.getType());
+ detailedProduct.setDetails(details);
+ detailedProducts.add(detailedProduct);
+ break;
+ }
+ }
+ }
+
+ private static void doneFetching(boolean value) {
+ if (!value) {
+ System.out.println("There was an error fetching products");
+ }
+ Platform.runLater(() -> FETCH.set(true));
+ }
+
+ private static void restorePurchases(String[] puchasesId) {
+ InAppBillingQueryResult result = new InAppBillingQueryResult();
+ for (String id: puchasesId) {
+ for (Product product : detailedProducts) {
+ if (product.getId().equals(id)) {
+ product.getDetails().setState(ProductDetails.State.APPROVED);
+
+ ProductOrder productOrder = new ProductOrder();
+ productOrder.setPlatform(com.gluonhq.attach.util.Platform.IOS);
+ productOrder.setProduct(product);
+ // TODO: Restoring purchases doesn't take the fields for now
+ //productOrder.setFields(fields);
+ result.getProductOrders().add(productOrder);
+ break;
+ }
+ }
+ }
+ Platform.runLater(() -> queryResultListener.onQueryResultReceived(result));
+ }
+
+ private static void setPurchase(String key, String purchasedId, String transactionId, String transactionReceipt) {
+ if (purchasedId == null || purchasedId.isEmpty()) {
+ Platform.runLater(() -> MAP_ORDERS.put(key, null));
+ return;
+ }
+
+ ProductOrder productOrder = new ProductOrder();
+ productOrder.setPlatform(com.gluonhq.attach.util.Platform.IOS);
+ for (Product product : detailedProducts) {
+ if (product.getId().equals(purchasedId)) {
+ productOrder.setProduct(product);
+ break;
+ }
+ }
+ Map fields = new HashMap<>();
+ fields.put("productId", purchasedId);
+ fields.put("orderId", transactionId);
+ fields.put("token", transactionReceipt);
+
+ // TODO: validate transactionReceipt from the server side
+ // https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
+ productOrder.setFields(fields);
+ Platform.runLater(() -> MAP_ORDERS.put(key, productOrder));
+ }
+}
diff --git a/modules/in-app-billing/src/main/native/ios/InAppBilling.h b/modules/in-app-billing/src/main/native/ios/InAppBilling.h
new file mode 100644
index 00000000..8b6c1766
--- /dev/null
+++ b/modules/in-app-billing/src/main/native/ios/InAppBilling.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface InAppBilling : UIViewController
+{
+ SKProductsRequest *productsRequest;
+}
+
+ - (void) setup;
+ - (void) fetchProducts;
+ - (void) purchaseProduct:(NSString *)productId;
+
+ - (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
+ - (void) paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error;
+ - (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue;
+ - (void) logMessage:(NSString *)format, ...;
+
+@end
+
+void sendProduct(SKProduct *product);
+void ready(BOOL value);
+void doneFetching(BOOL value);
+void sendPurchases(NSArray *purchasedIDs);
+void sendPurchase(NSString *purchasedID, NSString *transactionId, NSString *transactionReceipt);
diff --git a/modules/in-app-billing/src/main/native/ios/InAppBilling.m b/modules/in-app-billing/src/main/native/ios/InAppBilling.m
new file mode 100644
index 00000000..1031e627
--- /dev/null
+++ b/modules/in-app-billing/src/main/native/ios/InAppBilling.m
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "InAppBilling.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_InAppBilling(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int InAppBillingInited = 0;
+
+// InAppBilling
+jclass mat_jInAppBillingServiceClass;
+jmethodID mat_jInAppBillingService_setInAppReady = 0;
+jmethodID mat_jInAppBillingService_setProduct = 0;
+jmethodID mat_jInAppBillingService_doneFetching = 0;
+jmethodID mat_jInAppBillingService_restorePurchases = 0;
+jmethodID mat_jInAppBillingService_setPurchase = 0;
+
+InAppBilling *_InAppBilling;
+NSArray *arrayOfProductIds;
+NSMutableArray *arrayOfProducts;
+NSNumberFormatter *numberFormatter;
+NSMutableDictionary *orders;
+BOOL debugInAppBilling;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_inappbilling_impl_IOSInAppBillingService_initInAppBilling
+(JNIEnv *env, jclass jClass)
+{
+ if (InAppBillingInited)
+ {
+ return;
+ }
+ InAppBillingInited = 1;
+
+ mat_jInAppBillingServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/inappbilling/impl/IOSInAppBillingService"));
+ mat_jInAppBillingService_setInAppReady = (*env)->GetStaticMethodID(env, mat_jInAppBillingServiceClass, "setInAppReady", "(Z)V");
+ mat_jInAppBillingService_setProduct = (*env)->GetStaticMethodID(env, mat_jInAppBillingServiceClass, "setProduct", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ mat_jInAppBillingService_doneFetching = (*env)->GetStaticMethodID(env, mat_jInAppBillingServiceClass, "doneFetching", "(Z)V");
+ mat_jInAppBillingService_restorePurchases = (*env)->GetStaticMethodID(env, mat_jInAppBillingServiceClass, "restorePurchases", "([Ljava/lang/String;)V");
+ mat_jInAppBillingService_setPurchase = (*env)->GetStaticMethodID(env, mat_jInAppBillingServiceClass, "setPurchase", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+
+ debugInAppBilling = NO;
+ orders = [[NSMutableDictionary alloc] init];
+
+ AttachLog(@"Init InAppBilling");
+ _InAppBilling = [[InAppBilling alloc] init];
+
+ [_InAppBilling setup];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_inappbilling_impl_IOSInAppBillingService_fetchProducts
+(JNIEnv *env, jclass jClass, jobjectArray jProductIdsArray)
+{
+ int productIdCount = (*env)->GetArrayLength(env, jProductIdsArray);
+ NSMutableArray *productIds = [[NSMutableArray alloc] init];
+
+ for (int i=0; iGetObjectArrayElement(env, jProductIdsArray, i));
+ const jchar *productIdString = (*env)->GetStringChars(env, jproductId, NULL);
+ NSString *productId = [NSString stringWithCharacters:(UniChar *)productIdString length:(*env)->GetStringLength(env, jproductId)];
+ (*env)->ReleaseStringChars(env, jproductId, productIdString);
+ [productIds addObject:productId];
+ }
+ arrayOfProductIds = [NSArray arrayWithArray:productIds];
+
+ AttachLog(@"Fetching products");
+ [_InAppBilling fetchProducts];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_inappbilling_impl_IOSInAppBillingService_purchaseProduct
+(JNIEnv *env, jclass jClass, jstring jKey, jstring jProductId)
+{
+ const jchar *keyString = (*env)->GetStringChars(env, jKey, NULL);
+ NSString *key = [NSString stringWithCharacters:(UniChar *)keyString length:(*env)->GetStringLength(env, jKey)];
+ (*env)->ReleaseStringChars(env, jKey, keyString);
+
+ const jchar *productIdString = (*env)->GetStringChars(env, jProductId, NULL);
+ NSString *productId = [NSString stringWithCharacters:(UniChar *)productIdString length:(*env)->GetStringLength(env, jProductId)];
+ (*env)->ReleaseStringChars(env, jProductId, productIdString);
+
+ AttachLog(@"Purchasing product %@ with key %@", productId, key);
+ [orders setObject:productId forKey:key];
+ [_InAppBilling purchaseProduct:productId];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_inappbilling_impl_IOSInAppBillingService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugInAppBilling = YES;
+}
+
+void ready(BOOL value) {
+ (*env)->CallStaticVoidMethod(env, mat_jInAppBillingServiceClass, mat_jInAppBillingService_setInAppReady, (value) ? JNI_TRUE : JNI_FALSE);
+}
+
+void sendProduct(SKProduct *product) {
+ const char *product0Chars = [product.productIdentifier UTF8String];
+ jstring arg0 = (*env)->NewStringUTF(env, product0Chars);
+ const char *product1Chars = [product.localizedTitle UTF8String];
+ jstring arg1 = (*env)->NewStringUTF(env, product1Chars);
+ const char *product2Chars = [product.localizedDescription UTF8String];
+ jstring arg2 = (*env)->NewStringUTF(env, product2Chars);
+ [numberFormatter setLocale:product.priceLocale];
+ const char *product3Chars = [[numberFormatter stringFromNumber:product.price] UTF8String];
+ jstring arg3 = (*env)->NewStringUTF(env, product3Chars);
+ const char *product4Chars = [product.priceLocale.currencyCode UTF8String];
+ jstring arg4 = (*env)->NewStringUTF(env, product4Chars);
+ (*env)->CallStaticVoidMethod(env, mat_jInAppBillingServiceClass, mat_jInAppBillingService_setProduct, arg0, arg1, arg2, arg3, arg4);
+ (*env)->DeleteLocalRef(env, arg0);
+ (*env)->DeleteLocalRef(env, arg1);
+ (*env)->DeleteLocalRef(env, arg2);
+ (*env)->DeleteLocalRef(env, arg3);
+ (*env)->DeleteLocalRef(env, arg4);
+
+ AttachLog(@"Finished sending product");
+}
+
+void doneFetching(BOOL value)
+{
+ (*env)->CallStaticVoidMethod(env, mat_jInAppBillingServiceClass, mat_jInAppBillingService_doneFetching, (value) ? JNI_TRUE : JNI_FALSE);
+}
+
+void sendPurchases(NSArray *purchasedIDs)
+{
+ int size = [purchasedIDs count];
+ jobjectArray ret = (*env)->NewObjectArray(env, size, (*env)->FindClass(env, "java/lang/String"), NULL);
+
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ const char *purchasedID = [purchasedIDs[i] UTF8String];
+ (*env)->SetObjectArrayElement(env, ret, i, (*env)->NewStringUTF(env, purchasedID));
+ }
+ (*env)->CallStaticVoidMethod(env, mat_jInAppBillingServiceClass, mat_jInAppBillingService_restorePurchases, ret);
+ (*env)->DeleteLocalRef(env, ret);
+}
+
+void sendPurchase(NSString *purchasedID, NSString *transactionId, NSString *transactionReceipt)
+{
+ AttachLog(@"Sending purchase %@", purchasedID);
+ NSString* key;
+ for (NSString* k in orders)
+ {
+ NSString *v = [orders objectForKey:k];
+ if ([v isEqualToString:purchasedID])
+ {
+ key = k;
+ break;
+ }
+ }
+ if (!key)
+ {
+ AttachLog(@"Error retrieving key from orders for product %@", purchasedID);
+ return;
+ }
+
+ const char *keyChars = [key UTF8String];
+ jstring arg0 = (*env)->NewStringUTF(env, keyChars);
+ [orders removeObjectForKey:key];
+ const char *productIdChars = [purchasedID UTF8String];
+ jstring arg1 = (*env)->NewStringUTF(env, productIdChars);
+ const char *transactionIdChars = [transactionId UTF8String];
+ jstring arg2 = (*env)->NewStringUTF(env, transactionIdChars);
+ const char *transactionReceiptChars = [transactionReceipt UTF8String];
+ jstring arg3 = (*env)->NewStringUTF(env, transactionReceiptChars);
+ (*env)->CallStaticVoidMethod(env, mat_jInAppBillingServiceClass, mat_jInAppBillingService_setPurchase, arg0, arg1, arg2, arg3);
+ (*env)->DeleteLocalRef(env, arg0);
+ (*env)->DeleteLocalRef(env, arg1);
+ (*env)->DeleteLocalRef(env, arg2);
+ (*env)->DeleteLocalRef(env, arg3);
+}
+
+@implementation InAppBilling
+
+- (void) setup
+{
+ if (![SKPaymentQueue canMakePayments])
+ {
+ AttachLog(@"Can't make payments. Please enable In App Purchase in Settings");
+ ready(NO);
+ }
+ else
+ {
+ [self logMessage:@"In App Purchase enabled"];
+
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
+
+ numberFormatter = [[NSNumberFormatter alloc] init];
+ [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
+ [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
+
+ ready(YES);
+ }
+}
+
+- (void) fetchProducts
+{
+ productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:arrayOfProductIds]];
+ productsRequest.delegate = self;
+
+ [self logMessage:@"Start fetching products"];
+ [productsRequest start];
+}
+
+- (void) purchaseProduct:(NSString *)productId
+{
+ [self logMessage:@"Purchase product %@", productId];
+ if ([arrayOfProducts count]) {
+ [self logMessage:@"Available products %d", [arrayOfProducts count]];
+ for (SKProduct *product in arrayOfProducts)
+ {
+ [self logMessage:@"Trying product %@", product.productIdentifier];
+ if ([product.productIdentifier isEqualToString:productId])
+ {
+ [self logMessage:@"Start purchasing product %@", productId];
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
+ break;
+ }
+ }
+ }
+ else
+ {
+ [self logMessage:@"No products to purchase"];
+ }
+}
+
+-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
+{
+
+ arrayOfProducts = [[NSMutableArray alloc] init];
+
+ NSArray *products = response.products;
+
+ if (products.count != 0)
+ {
+ for (SKProduct *product in products)
+ {
+ [arrayOfProducts addObject:product];
+ sendProduct(product);
+ }
+ } else {
+ AttachLog(@"Products not found");
+ }
+
+ for (SKProduct *product in response.invalidProductIdentifiers)
+ {
+ [self logMessage:@"Invalid product found: %@", product];
+ }
+
+ [productsRequest release];
+
+ // restoring purchases
+ [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
+
+}
+
+// SKPaymentTransactionObserver methods
+// called when the transaction status is updated
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
+{
+ NSString *state, *transactionIdentifier, *transactionReceipt, *productId;
+ NSData *receiptData;
+
+ [self logMessage:@"paymentQueue"];
+ for (SKPaymentTransaction *transaction in transactions)
+ {
+ switch (transaction.transactionState)
+ {
+ case SKPaymentTransactionStatePurchased:
+ [self logMessage:@"completeTransaction"];
+ state = @"PaymentTransactionStatePurchased";
+ transactionIdentifier = transaction.transactionIdentifier;
+ receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
+ if (!receiptData) {
+ transactionReceipt = @"No receipt";
+ } else {
+ transactionReceipt = [receiptData base64EncodedStringWithOptions:0];
+ }
+ productId = transaction.payment.productIdentifier;
+ [self logMessage:@"transaction state: %@ with id: %@ for product: %@", state, transactionIdentifier, productId];
+
+ sendPurchase(productId, transactionIdentifier, transactionReceipt);
+
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+ break;
+ case SKPaymentTransactionStateFailed:
+ [self logMessage:@"failedTransaction: error %d %@", transaction.error.code, transaction.error.localizedDescription];
+ state = @"PaymentTransactionStateFailed";
+ sendPurchase(@"", @"", @"");
+
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+ break;
+ case SKPaymentTransactionStateRestored:
+ [self logMessage:@"restoreTransaction"];
+ state = @"PaymentTransactionStateRestored";
+ transactionIdentifier = transaction.originalTransaction.transactionIdentifier;
+ receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
+ if (!receiptData) {
+ transactionReceipt = @"No receipt";
+ } else {
+ transactionReceipt = [receiptData base64EncodedStringWithOptions:0];
+ }
+ productId = transaction.originalTransaction.payment.productIdentifier;
+ [self logMessage:@"transaction state: %@ with id: %@ for product: %@", state, transactionIdentifier, productId];
+
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+ break;
+ default:
+ break;
+ }
+
+ }
+}
+
+- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
+{
+ [self logMessage:@"paymentQueueRestoreCompletedTransactionsFinished"];
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ dateFormatter.dateStyle = NSDateFormatterMediumStyle;
+ dateFormatter.timeStyle = NSDateFormatterShortStyle;
+
+ NSMutableArray *purchasedItemIDs = [[NSMutableArray alloc] init];
+
+ [self logMessage:@"received restored transactions: %li", queue.transactions.count];
+ for (SKPaymentTransaction *transaction in queue.transactions)
+ {
+ if (transaction.transactionState == SKPaymentTransactionStateRestored || transaction.transactionState == SKPaymentTransactionStatePurchased) {
+ NSString *productID = transaction.payment.productIdentifier;
+ [self logMessage:@"product %@ purchased on %@", productID, [dateFormatter stringFromDate:transaction.transactionDate]];
+ [purchasedItemIDs addObject:productID];
+ } else {
+ [self logMessage:@"Transaction for %@ failed, error: %d %@", transaction.payment.productIdentifier, transaction.error.code, transaction.error.localizedDescription];
+ }
+ }
+ sendPurchases([purchasedItemIDs copy]);
+
+ doneFetching(YES);
+
+}
+
+-(void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
+{
+ AttachLog(@"restoreCompletedTransactionsFailedWithError %@", error);
+ doneFetching(NO);
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugInAppBilling)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv(format, args);
+ va_end(args);
+ }
+}
+@end
diff --git a/modules/lifecycle/src/main/native/ios/Lifecycle.h b/modules/lifecycle/src/main/native/ios/Lifecycle.h
index e0615857..a9347ac3 100644
--- a/modules/lifecycle/src/main/native/ios/Lifecycle.h
+++ b/modules/lifecycle/src/main/native/ios/Lifecycle.h
@@ -28,6 +28,7 @@
#import
#include "jni.h"
+#include "AttachMacros.h"
@interface Lifecycle : UIViewController {}
- (void) initEvents;
diff --git a/modules/lifecycle/src/main/native/ios/Lifecycle.m b/modules/lifecycle/src/main/native/ios/Lifecycle.m
index 3ffd4889..52ea1a5a 100644
--- a/modules/lifecycle/src/main/native/ios/Lifecycle.m
+++ b/modules/lifecycle/src/main/native/ios/Lifecycle.m
@@ -99,7 +99,7 @@ - (void)initEvents {
}
- (void)stopEvents {
- NSLog(@"Unregistering sending event");
+ AttachLog(@"Unregistering sending event");
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification
diff --git a/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/IOSLocalNotificationsService.java b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/IOSLocalNotificationsService.java
new file mode 100644
index 00000000..b6e07656
--- /dev/null
+++ b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/IOSLocalNotificationsService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.localnotifications.impl;
+
+
+import com.gluonhq.attach.localnotifications.Notification;
+import com.gluonhq.attach.util.Constants;
+
+/**
+ * iOS implementation of LocalNotificationsService.
+ */
+public class IOSLocalNotificationsService extends LocalNotificationsServiceBase {
+
+ static {
+ System.loadLibrary("LocalNotifications");
+ initLocalNotification();
+ }
+
+ public IOSLocalNotificationsService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ }
+
+ @Override
+ protected void unscheduleNotification(Notification notification) {
+ if (notification != null) {
+ unregisterNotification(notification.getId());
+ }
+ }
+
+ @Override
+ protected void scheduleNotification(Notification notification) {
+ registerNotification(notification.getTitle() == null ? "" : notification.getTitle(),
+ notification.getText(), notification.getId(), notification.getDateTime().toEpochSecond());
+ }
+
+ private native void registerNotification(String title, String text, String identifier, double seconds);
+
+ private native void unregisterNotification(String identifier);
+
+ private static native void initLocalNotification();
+ private static native void enableDebug();
+
+
+}
diff --git a/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/LocalNotificationsServiceBase.java b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/LocalNotificationsServiceBase.java
index 73e6d6d5..26d0343b 100644
--- a/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/LocalNotificationsServiceBase.java
+++ b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/impl/LocalNotificationsServiceBase.java
@@ -27,7 +27,7 @@
*/
package com.gluonhq.attach.localnotifications.impl;
-import com.gluonhq.attach.runtime.RuntimeArgsService;
+import com.gluonhq.attach.runtimeargs.RuntimeArgsService;
import java.util.logging.Logger;
import com.gluonhq.attach.util.Services;
import com.gluonhq.attach.localnotifications.LocalNotificationsService;
diff --git a/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/package-info.java b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/package-info.java
index aab86e2b..63b0bdde 100644
--- a/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/package-info.java
+++ b/modules/local-notifications/src/main/java/com/gluonhq/attach/localnotifications/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Local Notifications plugin,
+ * Primary API package for Attach - Local Notifications plugin,
* contains the interface {@link com.gluonhq.attach.localnotifications.LocalNotificationsService} and related classes.
*/
package com.gluonhq.attach.localnotifications;
\ No newline at end of file
diff --git a/modules/local-notifications/src/main/native/ios/LocalNotifications.h b/modules/local-notifications/src/main/native/ios/LocalNotifications.h
new file mode 100644
index 00000000..1af1d174
--- /dev/null
+++ b/modules/local-notifications/src/main/native/ios/LocalNotifications.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+API_AVAILABLE(ios(10.0))
+@interface LocalNotifications : NSObject
+{
+}
+
+@end
\ No newline at end of file
diff --git a/modules/local-notifications/src/main/native/ios/LocalNotifications.m b/modules/local-notifications/src/main/native/ios/LocalNotifications.m
new file mode 100644
index 00000000..59cf1f10
--- /dev/null
+++ b/modules/local-notifications/src/main/native/ios/LocalNotifications.m
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LocalNotifications.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_LocalNotifications(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int notificationsInited = 0;
+BOOL debugLocalNotifications;
+
+// Notifications
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_localnotifications_impl_IOSLocalNotificationsService_initLocalNotification
+(JNIEnv *env, jclass jClass)
+{
+ if (notificationsInited)
+ {
+ return;
+ }
+ notificationsInited = 1;
+
+ if (@available(iOS 10.0, *))
+ {
+ AttachLog(@"Initialize UNUserNotificationCenter iOS 10+");
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionBadge + UNAuthorizationOptionSound;
+ [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ if (!granted) {
+ AttachLog(@"Error granting notification options");
+ }
+ }];
+
+ [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
+ // Authorization status of the UNNotificationSettings object
+ switch (settings.authorizationStatus) {
+ case UNAuthorizationStatusAuthorized:
+ AttachLog(@"UNNotificationSettings Status Authorized");
+ break;
+ case UNAuthorizationStatusDenied:
+ AttachLog(@"UNNotificationSettings Status Denied");
+ break;
+ case UNAuthorizationStatusNotDetermined:
+ AttachLog(@"UNNotificationSettings Status Undetermined");
+ break;
+ default:
+ break;
+ }
+
+ // Status of specific settings
+ if (settings.alertSetting != UNAuthorizationStatusAuthorized) {
+ AttachLog(@"Alert settings not authorized");
+ }
+
+ if (settings.badgeSetting != UNAuthorizationStatusAuthorized) {
+ AttachLog(@"Badge settings not authorized");
+ }
+
+ if (settings.soundSetting != UNAuthorizationStatusAuthorized) {
+ AttachLog(@"Sound settings not authorized");
+ }
+ }];
+ }
+ else
+ {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ AttachLog(@"Initialize UIUserNotificationSettings iOS 10-");
+ UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil];
+ [[UIApplication sharedApplication] registerUserNotificationSettings: settings];
+ #pragma clang diagnostic pop
+ }
+
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_localnotifications_impl_IOSLocalNotificationsService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugLocalNotifications = YES;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_localnotifications_impl_IOSLocalNotificationsService_registerNotification
+(JNIEnv *env, jobject obj, jstring jTitle, jstring jText, jstring jIdentifier, jdouble seconds)
+{
+ if (debugLocalNotifications) {
+ AttachLog(@"Register notification");
+ }
+ const jchar *charsTitle = (*env)->GetStringChars(env, jTitle, NULL);
+ NSString *name = [NSString stringWithCharacters:(UniChar *)charsTitle length:(*env)->GetStringLength(env, jTitle)];
+ (*env)->ReleaseStringChars(env, jTitle, charsTitle);
+ const jchar *charsText = (*env)->GetStringChars(env, jText, NULL);
+ NSString *text = [NSString stringWithCharacters:(UniChar *)charsText length:(*env)->GetStringLength(env, jText)];
+ (*env)->ReleaseStringChars(env, jText, charsText);
+ const jchar *charsIdentifier = (*env)->GetStringChars(env, jIdentifier, NULL);
+ NSString *identifier = [NSString stringWithCharacters:(UniChar *)charsIdentifier length:(*env)->GetStringLength(env, jIdentifier)];
+ (*env)->ReleaseStringChars(env, jIdentifier, charsIdentifier);
+
+ if (@available(iOS 10.0, *))
+ {
+ UNMutableNotificationContent *content = [UNMutableNotificationContent new];
+ content.title = name;
+ content.body = text;
+ content.sound = [UNNotificationSound defaultSound];
+ content.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:identifier, @"userId", nil];
+
+ NSDate *date = [NSDate dateWithTimeIntervalSince1970:seconds];
+ NSDateComponents *triggerDate = [[NSCalendar currentCalendar]
+ components:NSCalendarUnitYear +
+ NSCalendarUnitMonth + NSCalendarUnitDay +
+ NSCalendarUnitHour + NSCalendarUnitMinute +
+ NSCalendarUnitSecond fromDate:date];
+ UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:triggerDate
+ repeats:NO];
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
+ content:content trigger:trigger];
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
+ if (error != nil) {
+ AttachLog(@"Something went wrong scheduling local notification: %@",error);
+ } else if (debugLocalNotifications) {
+ AttachLog(@"done register notifications for %@ with identifier %@", name, identifier);
+ }
+ }];
+ }
+ else
+ {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ UILocalNotification* localNotification = [[UILocalNotification alloc] init];
+ localNotification.fireDate = [NSDate dateWithTimeIntervalSince1970:seconds];
+ // Not supported by iOS 8.1
+ // localNotification.alertTitle = name;
+ if ([name length] == 0) {
+ localNotification.alertBody = text;
+ } else {
+ localNotification.alertBody = [name stringByAppendingFormat:@"%@%@",@"\n",text];
+ }
+ localNotification.soundName = UILocalNotificationDefaultSoundName;
+ localNotification.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:identifier, @"userId", nil];
+ localNotification.timeZone = [NSTimeZone defaultTimeZone];
+ localNotification.category = @"sessionReminderCategory";
+ [[UIApplication sharedApplication] scheduleLocalNotification: localNotification];
+ if (debugLocalNotifications) {
+ AttachLog(@"done register notifications for %@ with identifier %@", name, identifier);
+ }
+ #pragma clang diagnostic pop
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_localnotifications_impl_IOSLocalNotificationsService_unregisterNotification
+(JNIEnv *env, jclass jClass, jstring jIdentifier)
+{
+ const jchar *charsIdentifier = (*env)->GetStringChars(env, jIdentifier, NULL);
+ NSString *identifier = [NSString stringWithCharacters:(UniChar *)charsIdentifier length:(*env)->GetStringLength(env, jIdentifier)];
+ (*env)->ReleaseStringChars(env, jIdentifier, charsIdentifier);
+
+ if (@available(iOS 10.0, *))
+ {
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ NSArray *array = [NSArray arrayWithObjects:identifier, nil];
+ [center removePendingNotificationRequestsWithIdentifiers:array];
+ if (debugLocalNotifications) {
+ AttachLog(@"We did remove the notification with id: %@", identifier);
+ }
+ } else {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ NSArray *nots = [[UIApplication sharedApplication] scheduledLocalNotifications];
+ for (int i=0; i<[nots count]; i++) {
+ UILocalNotification* candidate = [nots objectAtIndex:i];
+ NSDictionary *myUserInfo = candidate.userInfo;
+ NSString *myId = [myUserInfo objectForKey:@"userId"];
+ if ([myId isEqualToString:identifier]) {
+ [[UIApplication sharedApplication] cancelLocalNotification:candidate];
+ if (debugLocalNotifications) {
+ AttachLog(@"We did remove the notification with id: %@", identifier);
+ }
+ }
+ }
+ #pragma clang diagnostic pop
+ }
+}
+
+@implementation LocalNotifications
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
+{
+ // called when a user selects an action in a delivered notification
+ [self logMessage:@"didReceiveNotificationResponse %@", response];
+ completionHandler();
+}
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
+{
+ // called when a notification is delivered to a foreground app
+ [self logMessage:@"willPresentNotification %@", notification];
+ completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionSound);
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugLocalNotifications)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv([@"[Debug] " stringByAppendingString:format], args);
+ va_end(args);
+ }
+}
+@end
diff --git a/modules/magnetometer/build.gradle b/modules/magnetometer/build.gradle
index dc23fa2a..58d57c1d 100644
--- a/modules/magnetometer/build.gradle
+++ b/modules/magnetometer/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(":util")
+ implementation project(":lifecycle")
}
ext.description = 'Common API to access magnetometer features'
\ No newline at end of file
diff --git a/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/impl/IOSMagnetometerService.java b/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/impl/IOSMagnetometerService.java
new file mode 100644
index 00000000..0fde5670
--- /dev/null
+++ b/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/impl/IOSMagnetometerService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2016, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.magnetometer.impl;
+
+import com.gluonhq.attach.lifecycle.LifecycleEvent;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import com.gluonhq.attach.magnetometer.MagnetometerReading;
+import com.gluonhq.attach.magnetometer.MagnetometerService;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+public class IOSMagnetometerService implements MagnetometerService {
+
+ static {
+ System.loadLibrary("Magnetometer");
+ initMagnetometer();
+ }
+
+ private static final ReadOnlyObjectWrapper reading = new ReadOnlyObjectWrapper<>();
+
+ public IOSMagnetometerService() {
+ LifecycleService.create().ifPresent(l -> {
+ l.addListener(LifecycleEvent.PAUSE, IOSMagnetometerService::stopObserver);
+ l.addListener(LifecycleEvent.RESUME, () -> startObserver(FREQUENCY));
+ });
+ startObserver(FREQUENCY);
+ }
+
+ @Override
+ public ReadOnlyObjectProperty readingProperty() {
+ return reading.getReadOnlyProperty();
+ }
+
+ @Override
+ public MagnetometerReading getReading() {
+ return reading.get();
+ }
+
+ // native
+ private static native void initMagnetometer();
+ private static native void startObserver(int rateInMillis);
+ private static native void stopObserver();
+
+ // callback
+ private static void notifyReading(double x, double y, double z, double m, double a, double p, double r) {
+ MagnetometerReading read = new MagnetometerReading(x, y, z, m, a, p, r);
+ Platform.runLater(() -> reading.setValue(read));
+ }
+}
diff --git a/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/package-info.java b/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/package-info.java
index 395630b8..d44a126a 100644
--- a/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/package-info.java
+++ b/modules/magnetometer/src/main/java/com/gluonhq/attach/magnetometer/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Magnetometer plugin,
+ * Primary API package for Attach - Magnetometer plugin,
* contains the interface {@link com.gluonhq.attach.magnetometer.MagnetometerService} and related classes.
*/
package com.gluonhq.attach.magnetometer;
\ No newline at end of file
diff --git a/modules/magnetometer/src/main/java/module-info.java b/modules/magnetometer/src/main/java/module-info.java
index 58e57d07..d932fb77 100644
--- a/modules/magnetometer/src/main/java/module-info.java
+++ b/modules/magnetometer/src/main/java/module-info.java
@@ -27,8 +27,9 @@
*/
module com.gluonhq.attach.magnetometer {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.lifecycle;
exports com.gluonhq.attach.magnetometer;
exports com.gluonhq.attach.magnetometer.impl to com.gluonhq.attach.util;
diff --git a/modules/magnetometer/src/main/native/ios/Magnetometer.h b/modules/magnetometer/src/main/native/ios/Magnetometer.h
new file mode 100644
index 00000000..cacf4f80
--- /dev/null
+++ b/modules/magnetometer/src/main/native/ios/Magnetometer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface Magnetometer : UIViewController {}
+ @property (strong, nonatomic) CMMotionManager *motionManager;
+ - (void) startObserver;
+ - (void) stopObserver;
+@end
+
+void sendReading(CMDeviceMotion *motionData);
diff --git a/modules/magnetometer/src/main/native/ios/Magnetometer.m b/modules/magnetometer/src/main/native/ios/Magnetometer.m
new file mode 100644
index 00000000..a404cfd3
--- /dev/null
+++ b/modules/magnetometer/src/main/native/ios/Magnetometer.m
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is fr_aee software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Magnetometer.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Magnetometer(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int MagnetometerInited = 0;
+
+// Magnetometer
+jclass mat_jMagnetometerServiceClass;
+jmethodID mat_jMagnetometerService_notifyReading = 0;
+Magnetometer *_magnetometer;
+double magRate = 0.1;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_magnetometer_impl_IOSMagnetometerService_initMagnetometer
+(JNIEnv *env, jclass jClass)
+{
+ if (MagnetometerInited)
+ {
+ return;
+ }
+ MagnetometerInited = 1;
+
+ mat_jMagnetometerServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/magnetometer/impl/IOSMagnetometerService"));
+ mat_jMagnetometerService_notifyReading = (*env)->GetStaticMethodID(env, mat_jMagnetometerServiceClass, "notifyReading", "(DDDDDDD)V");
+
+ _magnetometer = [[Magnetometer alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_magnetometer_impl_IOSMagnetometerService_startObserver
+(JNIEnv *env, jclass jClass, jint jfrequency)
+{
+ if (jfrequency > 0) {
+ magRate = 1.0 / ((double) jfrequency);
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_magnetometer startObserver];
+ });
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_magnetometer_impl_IOSMagnetometerService_stopObserver
+(JNIEnv *env, jclass jClass)
+{
+ [_magnetometer stopObserver];
+ return;
+}
+
+void sendReading0(CMMagnetometerData *magnetometerData) {
+ if (magnetometerData)
+ {
+ double x = magnetometerData.magneticField.x;
+ double y = magnetometerData.magneticField.y;
+ double z = magnetometerData.magneticField.z;
+ double m = sqrt(x * x + y * y + z * z);
+ (*env)->CallStaticVoidMethod(env, mat_jMagnetometerServiceClass, mat_jMagnetometerService_notifyReading, x, y, z, m, 0, 0, 0);
+ }
+}
+void sendReading(CMDeviceMotion *motionData) {
+ if (motionData)
+ {
+ double x = motionData.magneticField.field.x;
+ double y = motionData.magneticField.field.y;
+ double z = motionData.magneticField.field.z;
+ double m = sqrt(x * x + y * y + z * z);
+
+ double defaultYaw = motionData.attitude.yaw; // 0 right side device towards North, + Pi/2 towards West, Pi South, - Pi/2 East.
+ double yaw = M_PI / 2 + defaultYaw; // 0 front side device towards North
+ if (yaw > M_PI) {
+ yaw -= 2 * M_PI;
+ } else if (yaw < -M_PI) {
+ yaw += 2 * M_PI;
+ }
+ yaw = - yaw; // + Pi/2 towards East
+
+ double defaultPitch = motionData.attitude.pitch; // 0 parallel to ground, -Pi/2 top side towards ground
+ double pitch = -defaultPitch; // +PI/2 top side towards ground
+
+ double roll = motionData.attitude.roll;
+
+ (*env)->CallStaticVoidMethod(env, mat_jMagnetometerServiceClass, mat_jMagnetometerService_notifyReading,
+ x, y, z, m, yaw, pitch, roll);
+ }
+}
+
+@implementation Magnetometer
+
+- (void) startObserver
+{
+
+ if (!self.motionManager) {
+ self.motionManager = [[CMMotionManager alloc] init];
+ }
+
+ if ([self.motionManager isDeviceMotionAvailable])
+ {
+ self.motionManager.deviceMotionUpdateInterval = magRate; // in seconds
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical
+ toQueue:[NSOperationQueue mainQueue]
+ withHandler:^(CMDeviceMotion *motionData, NSError *error) {
+ sendReading(motionData);
+ }];
+ } else
+ {
+ AttachLog(@"Error: No Magnetometer Available");
+ }
+
+}
+
+- (void) stopObserver
+{
+ if (self.motionManager)
+ {
+ [self.motionManager stopDeviceMotionUpdates];
+ }
+}
+
+@end
+
diff --git a/modules/orientation/src/main/java/com/gluonhq/attach/orientation/package-info.java b/modules/orientation/src/main/java/com/gluonhq/attach/orientation/package-info.java
index c1203588..ef37042f 100644
--- a/modules/orientation/src/main/java/com/gluonhq/attach/orientation/package-info.java
+++ b/modules/orientation/src/main/java/com/gluonhq/attach/orientation/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Orientation plugin,
+ * Primary API package for Attach - Orientation plugin,
* contains the interface {@link com.gluonhq.attach.orientation.OrientationService} and related classes.
*/
package com.gluonhq.attach.orientation;
\ No newline at end of file
diff --git a/modules/orientation/src/main/native/ios/Orientation.h b/modules/orientation/src/main/native/ios/Orientation.h
index 473d7e05..9ab38afc 100644
--- a/modules/orientation/src/main/native/ios/Orientation.h
+++ b/modules/orientation/src/main/native/ios/Orientation.h
@@ -28,6 +28,7 @@
#import
#include "jni.h"
+#include "AttachMacros.h"
@interface Orientation : UIViewController {}
- (void) startObserver;
diff --git a/modules/orientation/src/main/native/ios/Orientation.m b/modules/orientation/src/main/native/ios/Orientation.m
index d6d2dcef..b0ffeb3d 100644
--- a/modules/orientation/src/main/native/ios/Orientation.m
+++ b/modules/orientation/src/main/native/ios/Orientation.m
@@ -85,7 +85,7 @@
void sendOrientation() {
NSString *orientation = [_orientation getOrientation];
- NSLog(@"Orientation is %@", orientation);
+ AttachLog(@"Orientation is %@", orientation);
const char *orientationChars = [orientation UTF8String];
jstring arg = (*env)->NewStringUTF(env, orientationChars);
(*env)->CallStaticVoidMethod(env, mat_jOrientationServiceClass, mat_jOrientationService_notifyOrientation, arg);
diff --git a/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/DummyPicturesService.java b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/DummyPicturesService.java
index 1e1a7a1f..e6e05280 100644
--- a/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/DummyPicturesService.java
+++ b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/DummyPicturesService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.pictures.impl;
import com.gluonhq.attach.pictures.PicturesService;
diff --git a/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/IOSPicturesService.java b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/IOSPicturesService.java
new file mode 100644
index 00000000..44231c6e
--- /dev/null
+++ b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/impl/IOSPicturesService.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.pictures.impl;
+
+import com.gluonhq.attach.pictures.PicturesService;
+import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.image.Image;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.util.Base64;
+import java.util.Optional;
+
+/**
+ * Note:Since iOS 10 requires {@code NSCameraUsageDescription},
+ * {@code NSPhotoLibraryUsageDescription} and
+ * {@code NSPhotoLibraryAddUsageDescription} in pList.
+ */
+public class IOSPicturesService implements PicturesService {
+
+ static {
+ System.loadLibrary("Pictures");
+ initPictures();
+ }
+
+ private static final ObjectProperty imageFile = new SimpleObjectProperty<>();
+ private static ObjectProperty result;
+
+ @Override
+ public Optional takePhoto(boolean savePhoto) {
+ result = new SimpleObjectProperty<>();
+ takePicture(savePhoto);
+ try {
+ Platform.enterNestedEventLoop(result);
+ } catch (Exception e) {
+ System.out.println("GalleryActivity: enterNestedEventLoop failed: " + e);
+ }
+ return Optional.ofNullable(result.get());
+ }
+
+ @Override
+ public Optional loadImageFromGallery() {
+ result = new SimpleObjectProperty<>();
+ selectPicture();
+ try {
+ Platform.enterNestedEventLoop(result);
+ } catch (Exception e) {
+ System.out.println("GalleryActivity: enterNestedEventLoop failed: " + e);
+ }
+ return Optional.ofNullable(result.get());
+ }
+
+ @Override
+ public Optional getImageFile() {
+ return Optional.ofNullable(imageFile.get());
+ }
+
+ // native
+ private static native void initPictures(); // init IDs for java callbacks from native
+ public static native void takePicture(boolean savePhoto);
+ public static native void selectPicture();
+
+ // callback
+ public static void setResult(String v, String filePath) {
+ if (v != null && !v.isEmpty()) {
+ try {
+ byte[] imageBytes = Base64.getDecoder().decode(v.replaceAll("\\s+", "").getBytes());
+ imageFile.set(new File(filePath));
+ result.set(new Image(new ByteArrayInputStream(imageBytes)));
+ } catch (Exception ex) {
+ System.err.println("Error setResult: " + ex);
+ }
+ }
+ Platform.runLater(() -> {
+ try {
+ Platform.exitNestedEventLoop(result, null);
+ } catch (Exception e) {
+ System.out.println("GalleryActivity: exitNestedEventLoop failed: " + e);
+ }
+ });
+ }
+}
diff --git a/modules/pictures/src/main/java/com/gluonhq/attach/pictures/package-info.java b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/package-info.java
index 18ea5088..43a75d70 100644
--- a/modules/pictures/src/main/java/com/gluonhq/attach/pictures/package-info.java
+++ b/modules/pictures/src/main/java/com/gluonhq/attach/pictures/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Pictures plugin,
+ * Primary API package for Attach - Pictures plugin,
* contains the interface {@link com.gluonhq.attach.pictures.PicturesService} and related classes.
*/
package com.gluonhq.attach.pictures;
\ No newline at end of file
diff --git a/modules/pictures/src/main/native/ios/Pictures.h b/modules/pictures/src/main/native/ios/Pictures.h
new file mode 100644
index 00000000..6c181e40
--- /dev/null
+++ b/modules/pictures/src/main/native/ios/Pictures.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+@interface Pictures : UIViewController {}
+ - (void) takePicture;
+ - (void) selectPicture;
+@end
+
+void sendPicturesResult(NSString *picResult, NSString *picPath);
\ No newline at end of file
diff --git a/modules/pictures/src/main/native/ios/Pictures.m b/modules/pictures/src/main/native/ios/Pictures.m
new file mode 100644
index 00000000..ffbb38c4
--- /dev/null
+++ b/modules/pictures/src/main/native/ios/Pictures.m
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Pictures.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Pictures(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int picturesInited = 0;
+
+// Pictures
+jclass mat_jPicturesServiceClass;
+jmethodID mat_jPicturesService_setResult = 0;
+Pictures *_pictures;
+BOOL savePhoto;
+
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_pictures_impl_IOSPicturesService_initPictures
+(JNIEnv *env, jclass jClass)
+{
+ if (picturesInited)
+ {
+ return;
+ }
+ picturesInited = 1;
+
+ mat_jPicturesServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/pictures/impl/IOSPicturesService"));
+ mat_jPicturesService_setResult = (*env)->GetStaticMethodID(env, mat_jPicturesServiceClass, "setResult", "(Ljava/lang/String;Ljava/lang/String;)V");
+}
+
+void sendPicturesResult(NSString *picResult, NSString *picPath) {
+ if (picResult)
+ {
+ const char *picChars = [picResult UTF8String];
+ jstring jpic = (*env)->NewStringUTF(env, picChars);
+ const char *pathChars = [picPath UTF8String];
+ jstring jpath = (*env)->NewStringUTF(env, pathChars);
+ (*env)->CallStaticVoidMethod(env, mat_jPicturesServiceClass, mat_jPicturesService_setResult, jpic, jpath);
+ (*env)->DeleteLocalRef(env, jpic);
+ (*env)->DeleteLocalRef(env, jpath);
+ AttachLog(@"Finished sending picture");
+ } else
+ {
+ (*env)->CallStaticVoidMethod(env, mat_jPicturesServiceClass, mat_jPicturesService_setResult, NULL, NULL);
+ }
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_pictures_impl_IOSPicturesService_takePicture
+(JNIEnv *env, jclass jClass, jboolean jSavePhoto)
+{
+ savePhoto = jSavePhoto;
+ _pictures = [[Pictures alloc] init];
+ [_pictures takePicture];
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_pictures_impl_IOSPicturesService_selectPicture
+(JNIEnv *env, jclass jClass)
+{
+ _pictures = [[Pictures alloc] init];
+ [_pictures selectPicture];
+ return;
+}
+
+@implementation Pictures
+
+- (void)takePicture {
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+
+ NSArray *views = [[[UIApplication sharedApplication] keyWindow] subviews];
+ if(![views count]) {
+ AttachLog(@"views size was 0");
+ return;
+ }
+
+ if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+ AttachLog(@"Device has no camera");
+ return;
+ }
+
+ UIView *_currentView = views[0];
+
+ UIImagePickerController *picker = [[UIImagePickerController alloc] init];
+ picker.delegate = self;
+ picker.allowsEditing = NO;
+ picker.sourceType = UIImagePickerControllerSourceTypeCamera;
+
+ [_currentView.window addSubview:picker.view];
+
+}
+
+- (void)selectPicture {
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+
+ NSArray *views = [[[UIApplication sharedApplication] keyWindow] subviews];
+ if(![views count]) {
+ AttachLog(@"views size was 0");
+ return;
+ }
+
+ UIView *_currentView = views[0];
+
+ UIImagePickerController *picker = [[UIImagePickerController alloc] init];
+ picker.delegate = self;
+ picker.allowsEditing = NO;
+ picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
+
+ [_currentView.window addSubview:picker.view];
+
+}
+
+- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
+
+ AttachLog(@"Encoding and sending retrieved picture");
+ UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
+
+ if (savePhoto == YES)
+ {
+ AttachLog(@"Saving picture...");
+ UIImageWriteToSavedPhotosAlbum(originalImage, nil, nil, nil);
+ }
+
+// The original image could be too big (ie 3264x2448) and not properly rotated,
+// what leads to: core.memory: GC Warning: Repeated allocation of very large block
+// and even: malloc: *** mach_vm_map(size=67108864) failed (error code=3) -> NPE at
+// com.sun.prism.impl.BaseGraphics.drawTexture(BaseGraphics.java)
+
+// Solution: limit max size to 1280x1280, and rotate properly:
+
+ UIImage *image = [self scaleAndRotateImage:originalImage];
+
+ NSData *imageData = UIImagePNGRepresentation(image);
+
+ NSString *base64StringOfImage = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
+
+ NSData *originalData = UIImagePNGRepresentation(originalImage);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ [formatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
+ NSString *stringFromDate = [formatter stringFromDate:[NSDate date]];
+
+ NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.png",@"Image",stringFromDate]];
+ [originalData writeToFile:filePath atomically:YES];
+
+ sendPicturesResult(base64StringOfImage, filePath);
+
+ [picker dismissViewControllerAnimated:YES completion:nil];
+ [picker.view removeFromSuperview];
+ [picker release];
+
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
+
+ AttachLog(@"Camera cancelled");
+
+ NSString *result = nil;
+ sendPicturesResult(result, result);
+
+ [picker dismissViewControllerAnimated:YES completion:nil];
+ [picker.view removeFromSuperview];
+ [picker release];
+
+}
+
+- (UIImage *)scaleAndRotateImage:(UIImage *)image
+{
+// FIXME: hardcoded value, add it as a parameter
+ int kMaxResolution = 1280;
+
+ CGImageRef imgRef = image.CGImage;
+
+ CGFloat width = CGImageGetWidth(imgRef);
+ CGFloat height = CGImageGetHeight(imgRef);
+
+ CGAffineTransform transform = CGAffineTransformIdentity;
+ CGRect bounds = CGRectMake(0, 0, width, height);
+ if (width > kMaxResolution || height > kMaxResolution) {
+ CGFloat ratio = width/height;
+ if (ratio > 1) {
+ bounds.size.width = kMaxResolution;
+ bounds.size.height = bounds.size.width / ratio;
+ }
+ else {
+ bounds.size.height = kMaxResolution;
+ bounds.size.width = bounds.size.height * ratio;
+ }
+ }
+
+ CGFloat scaleRatio = bounds.size.width / width;
+ CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
+ CGFloat boundHeight;
+ UIImageOrientation orient = image.imageOrientation;
+ switch(orient) {
+
+ case UIImageOrientationUp: //EXIF = 1
+ transform = CGAffineTransformIdentity;
+ break;
+
+ case UIImageOrientationUpMirrored: //EXIF = 2
+ transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
+ transform = CGAffineTransformScale(transform, -1.0, 1.0);
+ break;
+
+ case UIImageOrientationDown: //EXIF = 3
+ transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
+ transform = CGAffineTransformRotate(transform, M_PI);
+ break;
+
+ case UIImageOrientationDownMirrored: //EXIF = 4
+ transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
+ transform = CGAffineTransformScale(transform, 1.0, -1.0);
+ break;
+
+ case UIImageOrientationLeftMirrored: //EXIF = 5
+ boundHeight = bounds.size.height;
+ bounds.size.height = bounds.size.width;
+ bounds.size.width = boundHeight;
+ transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
+ transform = CGAffineTransformScale(transform, -1.0, 1.0);
+ transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
+ break;
+
+ case UIImageOrientationLeft: //EXIF = 6
+ boundHeight = bounds.size.height;
+ bounds.size.height = bounds.size.width;
+ bounds.size.width = boundHeight;
+ transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
+ transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
+ break;
+
+ case UIImageOrientationRightMirrored: //EXIF = 7
+ boundHeight = bounds.size.height;
+ bounds.size.height = bounds.size.width;
+ bounds.size.width = boundHeight;
+ transform = CGAffineTransformMakeScale(-1.0, 1.0);
+ transform = CGAffineTransformRotate(transform, M_PI / 2.0);
+ break;
+
+ case UIImageOrientationRight: //EXIF = 8
+ boundHeight = bounds.size.height;
+ bounds.size.height = bounds.size.width;
+ bounds.size.width = boundHeight;
+ transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
+ transform = CGAffineTransformRotate(transform, M_PI / 2.0);
+ break;
+
+ default:
+ [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];
+
+ }
+
+ UIGraphicsBeginImageContext(bounds.size);
+
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
+ CGContextScaleCTM(context, -scaleRatio, scaleRatio);
+ CGContextTranslateCTM(context, -height, 0);
+ }
+ else {
+ CGContextScaleCTM(context, scaleRatio, -scaleRatio);
+ CGContextTranslateCTM(context, 0, -height);
+ }
+
+ CGContextConcatCTM(context, transform);
+
+ CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
+ UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return imageCopy;
+}
+
+@end
diff --git a/modules/position/build.gradle b/modules/position/build.gradle
index 60863414..02261943 100644
--- a/modules/position/build.gradle
+++ b/modules/position/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(':util')
+ implementation project(':lifecycle')
}
ext.description = 'Common API to access position features'
\ No newline at end of file
diff --git a/modules/position/src/main/java/com/gluonhq/attach/position/impl/IOSPositionService.java b/modules/position/src/main/java/com/gluonhq/attach/position/impl/IOSPositionService.java
new file mode 100644
index 00000000..45163b02
--- /dev/null
+++ b/modules/position/src/main/java/com/gluonhq/attach/position/impl/IOSPositionService.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.position.impl;
+
+import com.gluonhq.attach.lifecycle.LifecycleEvent;
+import com.gluonhq.attach.lifecycle.LifecycleService;
+import com.gluonhq.attach.position.Parameters;
+import com.gluonhq.attach.position.Position;
+import com.gluonhq.attach.position.PositionService;
+import com.gluonhq.attach.util.Constants;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+/**
+ * An implementation of the
+ * {@link PositionService PositionService} for the
+ * iOS platform.
+ *
+ * Important note: it requires adding to the info.plist file:
+ *
+ * {@code
+ * NSLocationUsageDescription
+ * Reason to use Location Service (iOS 6+)
+ * NSLocationWhenInUseUsageDescription
+ * Reason to use Location Service (iOS 8+)
+ * }
+ *
+ * With Background mode enabled
+ * {@code
+ * UIBackgroundModes
+ *
+ * location
+ *
+ * NSLocationUsageDescription
+ * Reason to use Location Service (iOS 6+)
+ * NSLocationAlwaysUsageDescription
+ * Reason to use Location Service (iOS 8+) in background
+ * NSLocationAlwaysAndWhenInUseUsageDescription
+ * Reason to use Location Service (iOS 8+) in background
+ * }
+ */
+public class IOSPositionService implements PositionService {
+
+ static {
+ System.loadLibrary("Position");
+ initPosition();
+ }
+
+ private static ReadOnlyObjectWrapper position;
+ private static boolean running;
+ private Parameters parameters;
+
+ public IOSPositionService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ position = new ReadOnlyObjectWrapper<>();
+
+ LifecycleService.create().ifPresent(l -> {
+ l.addListener(LifecycleEvent.PAUSE, () -> {
+ if (! parameters.isBackgroundModeEnabled()) {
+ stopObserver();
+ }
+ });
+ l.addListener(LifecycleEvent.RESUME, () -> {
+ if (! parameters.isBackgroundModeEnabled()) {
+ startObserver(parameters.getAccuracy().name(), parameters.getTimeInterval(),
+ parameters.getDistanceFilter(), parameters.isBackgroundModeEnabled());
+ }
+ });
+ });
+ }
+
+ @Override
+ public void start() {
+ start(DEFAULT_PARAMETERS);
+ }
+
+ @Override
+ public void start(Parameters parameters) {
+ if (running) {
+ stop();
+ }
+ this.parameters = parameters;
+ startObserver(parameters.getAccuracy().name(), parameters.getTimeInterval(),
+ parameters.getDistanceFilter(), parameters.isBackgroundModeEnabled());
+ running = true;
+ }
+
+ @Override
+ public void stop() {
+ stopObserver();
+ running = false;
+ }
+
+ @Override
+ public ReadOnlyObjectProperty positionProperty() {
+ return position.getReadOnlyProperty();
+ }
+
+ @Override
+ public Position getPosition() {
+ return positionProperty().get();
+ }
+
+ // native
+ private static native void initPosition(); // init IDs for java callbacks from native
+ private static native void startObserver(String accuracy, long timeInterval, float distanceFilter, boolean backgroundModeEnabled);
+ private static native void stopObserver();
+ private static native void enableDebug();
+
+ // callback
+ private static void setLocation(double lat, double lon, double alt) {
+ Position p = new Position(lat, lon, alt);
+ Platform.runLater(() -> position.set(p));
+ }
+
+}
diff --git a/modules/position/src/main/java/com/gluonhq/attach/position/package-info.java b/modules/position/src/main/java/com/gluonhq/attach/position/package-info.java
index ef5ea769..22bf9375 100644
--- a/modules/position/src/main/java/com/gluonhq/attach/position/package-info.java
+++ b/modules/position/src/main/java/com/gluonhq/attach/position/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Position plugin,
+ * Primary API package for Attach - Position plugin,
* contains the interface {@link com.gluonhq.attach.position.PositionService} and related classes.
*/
package com.gluonhq.attach.position;
\ No newline at end of file
diff --git a/modules/position/src/main/java/module-info.java b/modules/position/src/main/java/module-info.java
index 3c038b8b..8f3694b5 100644
--- a/modules/position/src/main/java/module-info.java
+++ b/modules/position/src/main/java/module-info.java
@@ -27,8 +27,9 @@
*/
module com.gluonhq.attach.position {
- requires javafx.base;
+ requires javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.lifecycle;
exports com.gluonhq.attach.position;
exports com.gluonhq.attach.position.impl to com.gluonhq.attach.util;
diff --git a/modules/position/src/main/native/ios/Position.h b/modules/position/src/main/native/ios/Position.h
new file mode 100644
index 00000000..70a28d71
--- /dev/null
+++ b/modules/position/src/main/native/ios/Position.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface Position : UIViewController {}
+ @property(nonatomic, strong) CLLocationManager *locationManager;
+ - (void) startObserver:(NSString *)accuracy interval:(long)interval distance:(CGFloat)distance background:(BOOL)background;
+ - (void) stopObserver;
+@end
+
+void setLocation(CLLocation *newLocation);
\ No newline at end of file
diff --git a/modules/position/src/main/native/ios/Position.m b/modules/position/src/main/native/ios/Position.m
new file mode 100644
index 00000000..160ddfa4
--- /dev/null
+++ b/modules/position/src/main/native/ios/Position.m
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Position.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Position(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int positionInited = 0;
+
+// Position
+jclass mat_jPositionServiceClass;
+jmethodID mat_jPositionService_setLocation = 0;
+Position *_position;
+BOOL debugPosition;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_position_impl_IOSPositionService_initPosition
+(JNIEnv *env, jclass jClass)
+{
+ if (positionInited)
+ {
+ return;
+ }
+ positionInited = 1;
+
+ mat_jPositionServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/position/impl/IOSPositionService"));
+ mat_jPositionService_setLocation = (*env)->GetStaticMethodID(env, mat_jPositionServiceClass, "setLocation", "(DDD)V");
+
+ _position = [[Position alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_position_impl_IOSPositionService_startObserver
+(JNIEnv *env, jclass jClass, jstring jAccuracy, jlong jInterval, jfloat jDistance, jboolean jBackground)
+{
+ const jchar *charsAccuracy = (*env)->GetStringChars(env, jAccuracy, NULL);
+ NSString *sAccuracy = [NSString stringWithCharacters:(UniChar *)charsAccuracy length:(*env)->GetStringLength(env, jAccuracy)];
+ (*env)->ReleaseStringChars(env, jAccuracy, charsAccuracy);
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [_position startObserver:sAccuracy interval:jInterval distance:jDistance background:jBackground];
+ });
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_position_impl_IOSPositionService_stopObserver
+(JNIEnv *env, jclass jClass)
+{
+ [_position stopObserver];
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_position_impl_IOSPositionService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugPosition = YES;
+}
+
+void setLocation(CLLocation *newLocation) {
+ if (newLocation)
+ {
+ double lat = newLocation.coordinate.latitude;
+ double lon = newLocation.coordinate.longitude;
+ double alt = newLocation.altitude;
+ (*env)->CallStaticVoidMethod(env, mat_jPositionServiceClass, mat_jPositionService_setLocation, lat, lon, alt);
+ }
+
+}
+
+@implementation Position
+
+- (void)startObserver:(NSString *)accuracy interval:(long)interval distance:(CGFloat)distance background:(BOOL)background
+{
+
+ self.locationManager = [[CLLocationManager alloc] init];
+ self.locationManager.delegate = self;
+ self.locationManager.distanceFilter = distance;
+ if ([accuracy isEqualToString:@"HIGHEST"]) {
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
+ } else if ([accuracy isEqualToString:@"HIGH"]) {
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
+ } else if ([accuracy isEqualToString:@"MEDIUM"]) {
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
+ } else if ([accuracy isEqualToString:@"LOW"]) {
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
+ } else if ([accuracy isEqualToString:@"LOWEST"]) {
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
+ }
+ if (background) {
+ self.locationManager.allowsBackgroundLocationUpdates = YES;
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
+#ifdef __IPHONE_11_0
+ if (@available(iOS 11, *)) {
+ self.locationManager.showsBackgroundLocationIndicator = YES;
+ }
+#endif
+ }
+ }
+
+ if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
+ {
+ if (background) {
+ [self.locationManager requestAlwaysAuthorization];
+ } else {
+ // try to save battery by using GPS only when app is used:
+ [self.locationManager requestWhenInUseAuthorization];
+ }
+ }
+
+ if (debugPosition)
+ {
+ AttachLog(@"Start updating location with accuracy: %f", self.locationManager.desiredAccuracy);
+ }
+ [self.locationManager startUpdatingLocation];
+
+ // Request a location update
+ [self.locationManager requestLocation];
+
+}
+
+- (void)stopObserver
+{
+ if (debugPosition)
+ {
+ AttachLog(@"Stop updating location");
+ }
+ [self.locationManager stopUpdatingLocation];
+}
+
+- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
+ CLLocation *newLocation = [locations lastObject];
+ if (debugPosition)
+ {
+ AttachLog(@"NewLocation: %f, %f, %f", newLocation.coordinate.latitude, newLocation.coordinate.longitude, newLocation.altitude);
+ }
+ if (newLocation.horizontalAccuracy < 0)
+ {
+ if (debugPosition)
+ {
+ AttachLog(@"iOS location update, horizontal accuracy too small: %.2f", newLocation.horizontalAccuracy);
+ }
+ // return;
+ }
+
+ NSTimeInterval interval = [newLocation.timestamp timeIntervalSinceNow];
+ if (interval < -5)
+ {
+ if (debugPosition)
+ {
+ AttachLog(@"iOS location update, time interval to large (probably cached): %.2f", interval);
+ }
+ // return;
+ }
+
+ setLocation(newLocation);
+}
+
+- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
+{
+ if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
+ {
+ AttachLog(@"User has denied location services");
+ }
+ else
+ {
+ AttachLog(@"Location manager did fail with error: %@", error);
+ switch([error code])
+ {
+ case kCLErrorNetwork: // general, network-related error
+ {
+ AttachLog(@"ErrorNetwork");
+ }
+ break;
+ case kCLErrorDenied:
+ {
+ AttachLog(@"ErrorDenied");
+ }
+ break;
+ case kCLErrorLocationUnknown:
+ {
+ AttachLog(@"ErrorLocationUnknown");
+ }
+ break;
+ default:
+ {
+ AttachLog(@"Unknown error: %@", error);
+ }
+ break;
+ }
+ }
+}
+
+@end
diff --git a/modules/push-notifications/build.gradle b/modules/push-notifications/build.gradle
index 44de96a9..415a9879 100644
--- a/modules/push-notifications/build.gradle
+++ b/modules/push-notifications/build.gradle
@@ -1,11 +1,12 @@
apply plugin: 'org.openjfx.javafxplugin'
javafx {
- modules 'javafx.base'
+ modules 'javafx.graphics'
}
dependencies {
implementation project(":util")
+ implementation project(":runtime-args")
}
ext.description = 'Common API to access push notifications features'
\ No newline at end of file
diff --git a/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/DummyPushNotificationsService.java b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/DummyPushNotificationsService.java
index 343fa5c4..acff742f 100644
--- a/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/DummyPushNotificationsService.java
+++ b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/DummyPushNotificationsService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.pushnotifications.impl;
import com.gluonhq.attach.pushnotifications.PushNotificationsService;
diff --git a/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/IOSPushNotificationsService.java b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/IOSPushNotificationsService.java
new file mode 100644
index 00000000..eeb56fbb
--- /dev/null
+++ b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/impl/IOSPushNotificationsService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.pushnotifications.impl;
+
+import com.gluonhq.attach.pushnotifications.PushNotificationsService;
+import com.gluonhq.attach.runtimeargs.RuntimeArgsService;
+import com.gluonhq.attach.util.Constants;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyStringProperty;
+import javafx.beans.property.ReadOnlyStringWrapper;
+
+/**
+ * iOS implementation of PushNotificationsService.
+ */
+public class IOSPushNotificationsService implements PushNotificationsService {
+
+ static {
+ System.loadLibrary("PushNotifications");
+ }
+
+ /**
+ * A string property to wrap the token device when received from the native layer
+ */
+ private static final ReadOnlyStringWrapper TOKEN = new ReadOnlyStringWrapper();
+
+ public IOSPushNotificationsService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+
+ // Initialize RAS service
+ RuntimeArgsService.create();
+ }
+
+ @Override
+ public ReadOnlyStringProperty tokenProperty() {
+ return TOKEN.getReadOnlyProperty();
+ }
+
+ @Override
+ public void register(String authorizedEntity) {
+ initPushNotifications();
+ }
+
+ // native
+ private static native void initPushNotifications();
+ private static native void enableDebug();
+
+ /**
+ * @param s String with the error description
+ */
+ private static void failToRegisterForRemoteNotifications(String s) {
+ Platform.runLater(() -> System.out.println("Failed registering Push Notifications with error: " + s));
+ }
+
+ /**
+ * @param token String with the device token description
+ */
+ private static void didRegisterForRemoteNotifications(String token) {
+ if (token == null) {
+ return;
+ }
+ Platform.runLater(() -> TOKEN.setValue(token));
+ }
+}
\ No newline at end of file
diff --git a/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/package-info.java b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/package-info.java
new file mode 100644
index 00000000..e18fbab7
--- /dev/null
+++ b/modules/push-notifications/src/main/java/com/gluonhq/attach/pushnotifications/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2016, 2019 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Primary API package for Attach - Push Notifications plugin,
+ * contains the interface {@link com.gluonhq.attach.pushnotifications.PushNotificationsService} and related classes.
+ */
+package com.gluonhq.attach.pushnotifications;
\ No newline at end of file
diff --git a/modules/push-notifications/src/main/java/module-info.java b/modules/push-notifications/src/main/java/module-info.java
index 3816e787..6fa85485 100644
--- a/modules/push-notifications/src/main/java/module-info.java
+++ b/modules/push-notifications/src/main/java/module-info.java
@@ -27,9 +27,10 @@
*/
module com.gluonhq.attach.pushnotifications {
- requires transitive javafx.base;
+ requires transitive javafx.graphics;
requires com.gluonhq.attach.util;
+ requires com.gluonhq.attach.runtime.args;
exports com.gluonhq.attach.pushnotifications;
exports com.gluonhq.attach.pushnotifications.impl to com.gluonhq.attach.util;
diff --git a/modules/push-notifications/src/main/native/ios/PushNotifications.h b/modules/push-notifications/src/main/native/ios/PushNotifications.h
new file mode 100644
index 00000000..2d8d14fc
--- /dev/null
+++ b/modules/push-notifications/src/main/native/ios/PushNotifications.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface PushNotifications : NSObject { }
+@end
+
+@interface PushNotifications (NotificationsAdditions)
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+
+@end
\ No newline at end of file
diff --git a/modules/push-notifications/src/main/native/ios/PushNotifications.m b/modules/push-notifications/src/main/native/ios/PushNotifications.m
new file mode 100644
index 00000000..78077ec3
--- /dev/null
+++ b/modules/push-notifications/src/main/native/ios/PushNotifications.m
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PushNotifications.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_PushNotifications(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int notificationsInitied = 0;
+
+// Push Notifications
+
+jclass mat_jPushNotificationsClass;
+jmethodID mat_failToRegisterForRemoteNotifications = 0;
+jmethodID mat_didRegisterForRemoteNotifications = 0;
+BOOL debugPushNotifications;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_pushnotifications_impl_IOSPushNotificationsService_initPushNotifications
+(JNIEnv *env, jclass jClass)
+{
+ if (notificationsInitied)
+ {
+ return;
+ }
+ notificationsInitied = 1;
+
+ // Push Notifications
+ mat_jPushNotificationsClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/pushnotifications/impl/IOSPushNotificationsService"));
+ mat_failToRegisterForRemoteNotifications = (*env)->GetStaticMethodID(env, mat_jPushNotificationsClass, "failToRegisterForRemoteNotifications", "(Ljava/lang/String;)V");
+ mat_didRegisterForRemoteNotifications = (*env)->GetStaticMethodID(env, mat_jPushNotificationsClass, "didRegisterForRemoteNotifications", "(Ljava/lang/String;)V");
+
+ if (@available(iOS 10.0, *))
+ {
+ if (debugPushNotifications) {
+ AttachLog(@"Initialize UIUserNotificationSettings - Push >= 10");
+ }
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){
+ if (!error)
+ {
+ if (debugPushNotifications) {
+ AttachLog(@"Registering notifications");
+ }
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+ }
+ else
+ {
+ AttachLog(@"Registering notifications failed with error %@", [error localizedDescription]);
+ }
+ }];
+ }
+ else
+ {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+ if (debugPushNotifications) {
+ AttachLog(@"Initialize UIUserNotificationSettings - Push < 10");
+ }
+ UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil];
+ [[UIApplication sharedApplication] registerUserNotificationSettings: settings];
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+
+ #pragma clang diagnostic pop
+ }
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_pushnotifications_impl_IOSPushNotificationsService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugPushNotifications = YES;
+}
+
+@implementation PushNotifications (NotificationsAdditions)
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+
+ NSString * deviceTokenString = [[[[deviceToken description]
+ stringByReplacingOccurrencesOfString: @"<" withString: @""]
+ stringByReplacingOccurrencesOfString: @">" withString: @""]
+ stringByReplacingOccurrencesOfString: @" " withString: @""];
+
+ const char *deviceTokenChars = [deviceTokenString UTF8String];
+ jstring argToken = (*env)->NewStringUTF(env, deviceTokenChars);
+
+ [self logMessage:@"Sending token %@", deviceTokenString];
+ (*env)->CallStaticVoidMethod(env, mat_jPushNotificationsClass, mat_didRegisterForRemoteNotifications, argToken);
+ (*env)->DeleteLocalRef(env, argToken);
+ }
+ [pool drain];
+}
+
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
+{
+ AttachLog(@"Error registering remote notifications %@", [error localizedDescription]);
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ NSString *errorDescString = [error localizedDescription];
+ const char *errorDescChars = [errorDescString UTF8String];
+ jstring arg = (*env)->NewStringUTF(env, errorDescChars);
+ [self logMessage:@"Sending error %@", errorDescString];
+ (*env)->CallStaticVoidMethod(env, mat_jPushNotificationsClass, mat_failToRegisterForRemoteNotifications, arg);
+ (*env)->DeleteLocalRef(env, arg);
+ }
+ [pool drain];
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugPushNotifications)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv([@"[Debug] " stringByAppendingString:format], args);
+ va_end(args);
+ }
+}
+@end
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/RuntimeArgsService.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/RuntimeArgsService.java
similarity index 99%
rename from modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/RuntimeArgsService.java
rename to modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/RuntimeArgsService.java
index 067c5208..a05a49a2 100644
--- a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/RuntimeArgsService.java
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/RuntimeArgsService.java
@@ -25,7 +25,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.gluonhq.attach.runtime;
+package com.gluonhq.attach.runtimeargs;
import com.gluonhq.attach.util.Services;
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DefaultRuntimeArgsService.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DefaultRuntimeArgsService.java
similarity index 97%
rename from modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DefaultRuntimeArgsService.java
rename to modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DefaultRuntimeArgsService.java
index 4b2f6d4d..51aa81a4 100644
--- a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DefaultRuntimeArgsService.java
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DefaultRuntimeArgsService.java
@@ -25,9 +25,9 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.gluonhq.attach.runtime.impl;
+package com.gluonhq.attach.runtimeargs.impl;
-import com.gluonhq.attach.runtime.RuntimeArgsService;
+import com.gluonhq.attach.runtimeargs.RuntimeArgsService;
import java.util.HashMap;
import java.util.Map;
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DesktopRuntimeArgsService.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DesktopRuntimeArgsService.java
similarity index 97%
rename from modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DesktopRuntimeArgsService.java
rename to modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DesktopRuntimeArgsService.java
index 6aa53312..0da3791a 100644
--- a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DesktopRuntimeArgsService.java
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DesktopRuntimeArgsService.java
@@ -25,7 +25,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.gluonhq.attach.runtime.impl;
+package com.gluonhq.attach.runtimeargs.impl;
/**
* An implementation of RuntimeArgsService on desktop
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DummyRuntimeArgsService.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DummyRuntimeArgsService.java
similarity index 93%
rename from modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DummyRuntimeArgsService.java
rename to modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DummyRuntimeArgsService.java
index b5bafc74..ee9dd6f0 100644
--- a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/impl/DummyRuntimeArgsService.java
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/DummyRuntimeArgsService.java
@@ -25,9 +25,9 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.gluonhq.attach.runtime.impl;
+package com.gluonhq.attach.runtimeargs.impl;
-import com.gluonhq.attach.runtime.RuntimeArgsService;
+import com.gluonhq.attach.runtimeargs.RuntimeArgsService;
// no-op
public abstract class DummyRuntimeArgsService implements RuntimeArgsService {
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/IOSRuntimeArgsService.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/IOSRuntimeArgsService.java
new file mode 100644
index 00000000..42187563
--- /dev/null
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/impl/IOSRuntimeArgsService.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.runtimeargs.impl;
+
+import com.gluonhq.attach.runtimeargs.RuntimeArgsService;
+import com.gluonhq.attach.util.Constants;
+
+/**
+ * An implementation of the
+ * {@link RuntimeArgsService RuntimeArgsService} for the
+ * iOS platform.
+ */
+public class IOSRuntimeArgsService extends DefaultRuntimeArgsService {
+
+ static {
+ System.loadLibrary("RuntimeArgs");
+ initRuntimeArgs();
+ }
+
+ private static IOSRuntimeArgsService instance;
+
+ public IOSRuntimeArgsService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ instance = this;
+ }
+
+ // Native
+
+ private static native void initRuntimeArgs();
+ private static native void enableDebug();
+
+ // callback
+ private static void processRuntimeArgs(String key, String value) {
+ if (instance != null) {
+ instance.fire(key, value);
+ }
+ }
+}
diff --git a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/package-info.java b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/package-info.java
similarity index 87%
rename from modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/package-info.java
rename to modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/package-info.java
index a38aaa0d..81072865 100644
--- a/modules/runtime-args/src/main/java/com/gluonhq/attach/runtime/package-info.java
+++ b/modules/runtime-args/src/main/java/com/gluonhq/attach/runtimeargs/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Vibration plugin,
- * contains the interface {@link com.gluonhq.attach.runtime.RuntimeArgsService} and related classes.
+ * Primary API package for Attach - RuntimeArgs plugin,
+ * contains the interface {@link com.gluonhq.attach.runtimeargs.RuntimeArgsService} and related classes.
*/
-package com.gluonhq.attach.runtime;
\ No newline at end of file
+package com.gluonhq.attach.runtimeargs;
\ No newline at end of file
diff --git a/modules/runtime-args/src/main/java/module-info.java b/modules/runtime-args/src/main/java/module-info.java
index 2f7541d3..b8cafbb8 100644
--- a/modules/runtime-args/src/main/java/module-info.java
+++ b/modules/runtime-args/src/main/java/module-info.java
@@ -29,6 +29,6 @@
requires com.gluonhq.attach.util;
- exports com.gluonhq.attach.runtime;
- exports com.gluonhq.attach.runtime.impl to com.gluonhq.attach.util;
+ exports com.gluonhq.attach.runtimeargs;
+ exports com.gluonhq.attach.runtimeargs.impl to com.gluonhq.attach.util;
}
\ No newline at end of file
diff --git a/modules/runtime-args/src/main/native/ios/RuntimeArgs.h b/modules/runtime-args/src/main/native/ios/RuntimeArgs.h
new file mode 100644
index 00000000..aa802b2c
--- /dev/null
+++ b/modules/runtime-args/src/main/native/ios/RuntimeArgs.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+@interface RuntimeArgs : NSObject { }
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
+#pragma clang diagnostic pop
+
+@end
+
+@interface RasDelegate : NSObject
+-(void)register;
+-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;
+-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler;
+
+@end
+
+void processRuntimeArgs(NSString* key, NSString* value);
\ No newline at end of file
diff --git a/modules/runtime-args/src/main/native/ios/RuntimeArgs.m b/modules/runtime-args/src/main/native/ios/RuntimeArgs.m
new file mode 100644
index 00000000..a2fd28d2
--- /dev/null
+++ b/modules/runtime-args/src/main/native/ios/RuntimeArgs.m
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RuntimeArgs.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_RuntimeArgs(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int runtimeArgsInited = 0;
+
+jclass mat_jRuntimeArgsClass;
+jmethodID mat_jProcessRuntimeArgsMethod = 0;
+RasDelegate *_rasDelegate;
+BOOL debugRuntimeArgs;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_runtimeargs_impl_IOSRuntimeArgsService_initRuntimeArgs
+(JNIEnv *env, jclass jClass)
+{
+ if (runtimeArgsInited)
+ {
+ return;
+ }
+ runtimeArgsInited = 1;
+
+ mat_jRuntimeArgsClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/runtimeargs/impl/IOSRuntimeArgsService"));
+ mat_jProcessRuntimeArgsMethod = (*env)->GetStaticMethodID(env, mat_jRuntimeArgsClass, "processRuntimeArgs", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+ _rasDelegate = [[RasDelegate alloc] init];
+ [_rasDelegate register];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_runtimeargs_impl_IOSRuntimeArgsService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugRuntimeArgs = YES;
+}
+
+void processRuntimeArgs(NSString* key, NSString* value) {
+ const char *keyChars = [key UTF8String];
+ jstring jkey = (*env)->NewStringUTF(env, keyChars);
+ const char *valueChars = [value UTF8String];
+ jstring jvalue = (*env)->NewStringUTF(env, valueChars);
+ (*env)->CallStaticVoidMethod(env, mat_jRuntimeArgsClass, mat_jProcessRuntimeArgsMethod, jkey, jvalue);
+ (*env)->DeleteLocalRef(env, jkey);
+ (*env)->DeleteLocalRef(env, jvalue);
+}
+
+@implementation RuntimeArgs
+
+// TODO: Add the rest of methods that allow opening externally the application
+
+// iOS 4 - 9
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
+ sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ if (debugRuntimeArgs) {
+ AttachLog(@"OpenURL called: %@", url.absoluteString);
+ }
+ processRuntimeArgs(@"Launch.URL", url.absoluteString);
+ }
+ [pool drain];
+ return TRUE;
+}
+
+// iOS 10
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
+ options:(NSDictionary *)options
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ if (debugRuntimeArgs) {
+ AttachLog(@"OpenURL called: %@", url.absoluteString);
+ }
+ processRuntimeArgs(@"Launch.URL", url.absoluteString);
+ }
+ [pool drain];
+ return TRUE;
+}
+
+// called with app opened either on front or in the background, when user clicks on notification
+
+// Local Notifications iOS 4 - 10
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ NSDictionary *myUserInfo = notification.userInfo;
+ NSString *myId = [myUserInfo objectForKey:@"userId"];
+ if (debugRuntimeArgs) {
+ AttachLog(@"Sending this notification with id %@", myId);
+ }
+ processRuntimeArgs(@"Launch.LocalNotification", myId);
+ }
+ [pool drain];
+}
+#pragma clang diagnostic pop
+
+// Remote Notifications iOS < 10
+
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ NSError *err;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userInfo options:0 error: &err];
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+ if (debugRuntimeArgs) {
+ AttachLog(@"Received remote notification, forward to RAS");
+ }
+ processRuntimeArgs(@"Launch.PushNotification", jsonString);
+ if (debugRuntimeArgs) {
+ AttachLog(@"Processed remote notification");
+ }
+ }
+ [pool drain];
+
+ if(application.applicationState == UIApplicationStateInactive) {
+ if (debugRuntimeArgs) {
+ AttachLog(@"App was Inactive");
+ }
+ //Show the view with the content of the push
+ } else if (application.applicationState == UIApplicationStateBackground) {
+ if (debugRuntimeArgs) {
+ AttachLog(@"App was in Background");
+ }
+ //Refresh the local model
+ } else {
+ if (debugRuntimeArgs) {
+ AttachLog(@"App is Active");
+ }
+ //Show an in-app banner
+ }
+ if (debugRuntimeArgs) {
+ AttachLog(@"call completionhandler after remote notification");
+ }
+ completionHandler(UIBackgroundFetchResultNewData);
+
+}
+@end
+
+// Remote Notifications iOS 10
+
+@implementation RasDelegate
+
+- (void)register
+{
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ center.delegate = self;
+}
+
+-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
+
+ //When a notification is delivered to a foreground app, this will show it on top:
+ completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
+}
+
+-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
+
+ //Called when a notification is delivered to foreground or background app.
+ if (debugRuntimeArgs) {
+ AttachLog(@"Received remote notification: Userinfo %@",response.notification.request.content.userInfo);
+ }
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ NSError *err;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response.notification.request.content.userInfo options:0 error: &err];
+ if ([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
+ if (debugRuntimeArgs) {
+ AttachLog(@"Handling Push notification");
+ }
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+ processRuntimeArgs(@"Launch.PushNotification", jsonString);
+ } else {
+ if (debugRuntimeArgs) {
+ AttachLog(@"Handling Local notification");
+ }
+ NSDictionary *myUserInfo = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
+ NSString *myId = [myUserInfo objectForKey:@"userId"];
+ if (debugRuntimeArgs) {
+ AttachLog(@"Sending local notification with id %@", myId);
+ }
+ processRuntimeArgs(@"Launch.LocalNotification", myId);
+ }
+ }
+ [pool drain];
+ completionHandler();
+}
+
+@end
diff --git a/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/DummySettingsService.java b/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/DummySettingsService.java
index c366b938..1830beae 100644
--- a/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/DummySettingsService.java
+++ b/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/DummySettingsService.java
@@ -1,3 +1,30 @@
+/*
+ * Copyright (c) 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
package com.gluonhq.attach.settings.impl;
import com.gluonhq.attach.settings.SettingsService;
diff --git a/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/IOSSettingsService.java b/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/IOSSettingsService.java
index b68b0822..4b454dfc 100644
--- a/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/IOSSettingsService.java
+++ b/modules/settings/src/main/java/com/gluonhq/attach/settings/impl/IOSSettingsService.java
@@ -29,6 +29,7 @@
import com.gluonhq.attach.settings.SettingsService;
+import com.gluonhq.attach.util.Constants;
/**
* An implementation of the
@@ -42,6 +43,12 @@ public class IOSSettingsService implements SettingsService {
initSettings();
}
+ public IOSSettingsService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ }
+
@Override
public void store(String key, String value) {
settingsStore(key, value);
@@ -61,5 +68,6 @@ public String retrieve(String key) {
private static native void settingsStore(String key, String value);
private static native void settingsRemove(String key);
private static native String settingsRetrieve(String key);
+ private static native void enableDebug();
}
diff --git a/modules/settings/src/main/java/com/gluonhq/attach/settings/package-info.java b/modules/settings/src/main/java/com/gluonhq/attach/settings/package-info.java
index f069e61e..8d9d511d 100644
--- a/modules/settings/src/main/java/com/gluonhq/attach/settings/package-info.java
+++ b/modules/settings/src/main/java/com/gluonhq/attach/settings/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Settings plugin,
+ * Primary API package for Attach - Settings plugin,
* contains the interface {@link com.gluonhq.attach.settings.SettingsService} and related classes.
*/
package com.gluonhq.attach.settings;
\ No newline at end of file
diff --git a/modules/settings/src/main/native/ios/Settings.m b/modules/settings/src/main/native/ios/Settings.m
index 240fb107..b2149994 100644
--- a/modules/settings/src/main/native/ios/Settings.m
+++ b/modules/settings/src/main/native/ios/Settings.m
@@ -28,6 +28,7 @@
#import
#include "jni.h"
+#include "AttachMacros.h"
JNIEnv *env;
@@ -46,6 +47,7 @@
}
static int settingsInited = 0;
+BOOL debugSettings;
JNIEXPORT void JNICALL Java_com_gluonhq_attach_settings_impl_IOSSettingsService_initSettings
(JNIEnv *env, jclass jClass)
@@ -61,6 +63,13 @@
[defaults registerDefaults:defaultPreferences];
}
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_settings_impl_IOSSettingsService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugSettings = YES;
+}
+
+
JNIEXPORT void JNICALL Java_com_gluonhq_attach_settings_impl_IOSSettingsService_settingsStore
(JNIEnv *env, jclass jClass, jstring jKey, jstring jValue)
{
@@ -73,8 +82,9 @@
(*env)->ReleaseStringChars(env, jValue, charsVal);
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
- NSLog(@"Done storing %@ to %@", key, value);
-
+ if (debugSettings) {
+ AttachLog(@"Done storing %@ to %@", key, value);
+ }
}
JNIEXPORT void JNICALL Java_com_gluonhq_attach_settings_impl_IOSSettingsService_settingsRemove
@@ -85,7 +95,9 @@
(*env)->ReleaseStringChars(env, jKey, charsKey);
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
- NSLog(@"Done removing %@", key);
+ if (debugSettings) {
+ AttachLog(@"Done removing %@", key);
+ }
}
JNIEXPORT jstring JNICALL Java_com_gluonhq_attach_settings_impl_IOSSettingsService_settingsRetrieve
@@ -97,10 +109,12 @@
NSString *value = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (!value) {
- NSLog(@"%@ not found", key);
+ AttachLog(@"Error: %@ not found", key);
return NULL;
}
- NSLog(@"Done retreiving %@", key);
+ if (debugSettings) {
+ AttachLog(@"Done retreiving %@", key);
+ }
const char *valueChars = [value UTF8String];
return (*env)->NewStringUTF(env, valueChars);
}
\ No newline at end of file
diff --git a/modules/share/src/main/java/com/gluonhq/attach/share/impl/IOSShareService.java b/modules/share/src/main/java/com/gluonhq/attach/share/impl/IOSShareService.java
new file mode 100644
index 00000000..f6a3dc7e
--- /dev/null
+++ b/modules/share/src/main/java/com/gluonhq/attach/share/impl/IOSShareService.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.share.impl;
+
+
+import com.gluonhq.attach.share.ShareService;
+import com.gluonhq.attach.util.Constants;
+
+import java.io.File;
+
+
+public class IOSShareService implements ShareService {
+
+ static {
+ System.loadLibrary("Share");
+ initShare();
+ }
+
+ public IOSShareService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ }
+
+ @Override
+ public void share(String contentText) {
+ share(null, contentText);
+ }
+
+ @Override
+ public void share(String subject, String contentText) {
+ if (subject == null) {
+ subject = "";
+ }
+ if (contentText == null || contentText.isEmpty()) {
+ System.out.println("Error: contentText not valid");
+ return;
+ }
+ nativeShare(subject, contentText, "");
+ }
+
+ @Override
+ public void share(String type, File file) {
+ share(null, null, type, file);
+ }
+
+ @Override
+ public void share(String subject, String contentText, String type, File file) {
+ if (subject == null) {
+ subject = "";
+ }
+ if (contentText == null) {
+ contentText = "";
+ }
+ if (file != null && file.exists()) {
+ System.out.println("File to share: " + file);
+ } else {
+ System.out.println("Error: URL not valid");
+ return;
+ }
+ nativeShare(subject, contentText, file.getAbsolutePath());
+ }
+
+ // native
+ private static native void initShare(); // init IDs for java callbacks from native
+ private static native void enableDebug();
+ private static native void nativeShare(String subject, String message, String filePath);
+
+}
\ No newline at end of file
diff --git a/modules/share/src/main/java/com/gluonhq/attach/share/package-info.java b/modules/share/src/main/java/com/gluonhq/attach/share/package-info.java
index c1ccaa7e..207273fd 100644
--- a/modules/share/src/main/java/com/gluonhq/attach/share/package-info.java
+++ b/modules/share/src/main/java/com/gluonhq/attach/share/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Share plugin,
+ * Primary API package for Attach - Share plugin,
* contains the interface {@link com.gluonhq.attach.share.ShareService} and related classes.
*/
package com.gluonhq.attach.share;
\ No newline at end of file
diff --git a/modules/share/src/main/native/ios/Share.h b/modules/share/src/main/native/ios/Share.h
new file mode 100644
index 00000000..f33cd578
--- /dev/null
+++ b/modules/share/src/main/native/ios/Share.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+@interface Share : UIViewController
+{
+}
+ @property (nonatomic, strong) NSString *message;
+ @property (nonatomic, strong) NSString *subject;
+ @property (nonatomic, strong) NSString *filePath;
+ - (void) shareText:(NSString *)subject message:(NSString *)message filePath:(NSString *)filePath;
+@end
diff --git a/modules/share/src/main/native/ios/Share.m b/modules/share/src/main/native/ios/Share.m
new file mode 100644
index 00000000..260b7992
--- /dev/null
+++ b/modules/share/src/main/native/ios/Share.m
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "Share.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Share(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int ShareInited = 0;
+
+Share *_share;
+
+BOOL debugShare;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_share_impl_IOSShareService_initShare
+(JNIEnv *env, jclass jClass)
+{
+ if (ShareInited)
+ {
+ return;
+ }
+ ShareInited = 1;
+
+ AttachLog(@"Init Share");
+ _share = [[Share alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_share_impl_IOSShareService_nativeShare
+(JNIEnv *env, jclass jClass, jstring jsubject, jstring jmessage, jstring jfilePath) {
+
+ const jchar *charsSubject = (*env)->GetStringChars(env, jsubject, NULL);
+ NSString *subject = [NSString stringWithCharacters:(UniChar *)charsSubject length:(*env)->GetStringLength(env, jsubject)];
+ (*env)->ReleaseStringChars(env, jsubject, charsSubject);
+
+ const jchar *charsMessage = (*env)->GetStringChars(env, jmessage, NULL);
+ NSString *message = [NSString stringWithCharacters:(UniChar *)charsMessage length:(*env)->GetStringLength(env, jmessage)];
+ (*env)->ReleaseStringChars(env, jmessage, charsMessage);
+
+ const jchar *charsFilePath = (*env)->GetStringChars(env, jfilePath, NULL);
+ NSString *filePath = [NSString stringWithCharacters:(UniChar *)charsFilePath length:(*env)->GetStringLength(env, jfilePath)];
+ (*env)->ReleaseStringChars(env, jfilePath, charsFilePath);
+
+ [_share shareText: subject message:message filePath:filePath];
+
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_share_impl_IOSShareService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugShare = YES;
+}
+
+@implementation Share
+
+- (void) shareText: (NSString *)subject message:(NSString *)message filePath:(NSString *)filePath
+{
+ _subject = [[NSString alloc] initWithString:subject];
+ _message = [[NSString alloc] initWithString:message];
+ _filePath = [[NSString alloc] initWithString:filePath];
+
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+
+ NSArray *views = [[[UIApplication sharedApplication] keyWindow] subviews];
+ if(![views count]) {
+ AttachLog(@"views size was 0");
+ return;
+ }
+
+ UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
+ if(!rootViewController)
+ {
+ AttachLog(@"rootViewController was nil");
+ return;
+ }
+
+ NSMutableArray *items = [[NSMutableArray alloc] init];
+ [items addObject:self];
+
+ if ([_filePath length] > 0) {
+ [self logMessage:@"Share file: %@", _filePath];
+ NSURL *fileUrl = [NSURL fileURLWithPath:_filePath];
+ NSError *err;
+ if ([fileUrl checkResourceIsReachableAndReturnError:&err] == YES)
+ {
+ [self logMessage:@"Share fileUrl: %@", fileUrl];
+ [items addObject:fileUrl];
+ } else {
+ AttachLog(@"File resource not reachable: %@", err);
+ }
+ }
+
+ NSArray *itemsToShare = [NSArray arrayWithArray:items];
+
+ UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:itemsToShare applicationActivities:nil];
+ if ([activityViewController respondsToSelector:@selector(popoverPresentationController)]) {
+ activityViewController.popoverPresentationController.sourceView = views[0];
+ }
+
+ activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError){
+ [self logMessage:@"Activity Type selected: %@", activityType];
+ if (completed) {
+ [self logMessage:@"Selected activity was performed."];
+ } else {
+ if (activityType == NULL) {
+ [self logMessage:@"User dismissed the view controller without making a selection."];
+ } else {
+ [self logMessage:@"Activity was not performed."];
+ }
+ }
+ };
+
+ [rootViewController presentViewController:activityViewController animated:YES completion:nil];
+
+}
+
+- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
+{
+ [self logMessage:@"Share activityViewControllerPlaceholderItem"];
+ return @"";
+}
+
+- (NSString *) activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType
+{
+ [self logMessage:@"Share subjectForActivityType %@ - Subject: %@", activityType, _subject];
+ return _subject;
+}
+
+- (nullable id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType
+{
+ [self logMessage:@"Share itemForActivityType %@ - Message: %@", activityType, _message];
+ return _message;
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugShare)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv([@"[Debug] " stringByAppendingString:format], args);
+ va_end(args);
+ }
+}
+@end
+
diff --git a/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/impl/IOSStatusBarService.java b/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/impl/IOSStatusBarService.java
new file mode 100644
index 00000000..f4dd1969
--- /dev/null
+++ b/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/impl/IOSStatusBarService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.statusbar.impl;
+
+import com.gluonhq.attach.statusbar.StatusBarService;
+import javafx.scene.paint.Color;
+
+public class IOSStatusBarService implements StatusBarService {
+
+ static {
+ System.loadLibrary("StatusBar");
+ }
+
+ @Override
+ public void setColor(Color color) {
+ setNativeColor(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity());
+ }
+
+ private native void setNativeColor(double red, double green, double blue, double opacity);
+}
\ No newline at end of file
diff --git a/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/package-info.java b/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/package-info.java
index dba8ad9a..76792505 100644
--- a/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/package-info.java
+++ b/modules/statusbar/src/main/java/com/gluonhq/attach/statusbar/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Status Bar plugin,
+ * Primary API package for Attach - Status Bar plugin,
* contains the interface {@link com.gluonhq.attach.statusbar.StatusBarService} and related classes.
*/
package com.gluonhq.attach.statusbar;
\ No newline at end of file
diff --git a/modules/statusbar/src/main/native/ios/StatusBar.m b/modules/statusbar/src/main/native/ios/StatusBar.m
new file mode 100644
index 00000000..2d115c91
--- /dev/null
+++ b/modules/statusbar/src/main/native/ios/StatusBar.m
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_StatusBar(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_statusbar_impl_IOSStatusBarService_setNativeColor
+(JNIEnv *env, jclass jClass, jdouble red, jdouble green, jdouble blue, jdouble opacity)
+{
+ UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
+
+ if ([statusBar respondsToSelector:@selector(setBackgroundColor:)]) {
+ statusBar.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:opacity];
+ }
+}
\ No newline at end of file
diff --git a/modules/storage/src/main/java/com/gluonhq/attach/storage/impl/IOSStorageService.java b/modules/storage/src/main/java/com/gluonhq/attach/storage/impl/IOSStorageService.java
index dab8079e..d1a2ad36 100644
--- a/modules/storage/src/main/java/com/gluonhq/attach/storage/impl/IOSStorageService.java
+++ b/modules/storage/src/main/java/com/gluonhq/attach/storage/impl/IOSStorageService.java
@@ -28,6 +28,7 @@
package com.gluonhq.attach.storage.impl;
import com.gluonhq.attach.storage.StorageService;
+import com.gluonhq.attach.util.Constants;
import java.io.File;
import java.util.HashMap;
@@ -40,6 +41,12 @@ public class IOSStorageService implements StorageService {
System.loadLibrary("Storage");
}
+ public IOSStorageService() {
+ if ("true".equals(System.getProperty(Constants.ATTACH_DEBUG))) {
+ enableDebug();
+ }
+ }
+
@Override
public Optional getPrivateStorage() {
try {
@@ -90,4 +97,5 @@ private synchronized String getPublicStorageURL(String dir) {
private native String privateStorageURL();
private native String publicStorageURL(String dir);
+ private static native void enableDebug();
}
diff --git a/modules/storage/src/main/native/ios/Storage.m b/modules/storage/src/main/native/ios/Storage.m
index a4570197..026040ea 100644
--- a/modules/storage/src/main/native/ios/Storage.m
+++ b/modules/storage/src/main/native/ios/Storage.m
@@ -27,6 +27,7 @@
*/
#import
#include "jni.h"
+#include "AttachMacros.h"
JNIEnv *env;
@@ -44,6 +45,14 @@
#endif
}
+BOOL debugStorage;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_storage_impl_IOSStorageService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugStorage = YES;
+}
+
JNIEXPORT jstring JNICALL Java_com_gluonhq_attach_storage_impl_IOSStorageService_privateStorageURL
(JNIEnv *env, jclass jClass)
{
@@ -53,14 +62,16 @@
NSString *folderPath = [documentsDir stringByAppendingPathComponent:folder];
if (!folderPath) {
- NSLog(@"Error getting the private storage path");
+ AttachLog(@"Error getting the private storage path");
return NULL;
}
NSFileManager *manager = [NSFileManager defaultManager];
[manager createDirectoryAtPath: folderPath withIntermediateDirectories: NO attributes: nil error: nil];
- NSLog(@"Done creating private storage %@", folderPath);
+ if (debugStorage) {
+ AttachLog(@"Done creating private storage %@", folderPath);
+ }
const char *valueChars = [folderPath UTF8String];
return (*env)->NewStringUTF(env, valueChars);
}
@@ -81,10 +92,12 @@
[manager createDirectoryAtPath: folderPath withIntermediateDirectories: NO attributes: nil error: nil];
if (!folderPath) {
- NSLog(@"Error creating public storage path");
+ AttachLog(@"Error creating public storage path");
return NULL;
}
- NSLog(@"Done creating public storage %@", folderPath);
+ if (debugStorage) {
+ AttachLog(@"Done creating public storage %@", folderPath);
+ }
const char *valueChars = [folderPath UTF8String];
return (*env)->NewStringUTF(env, valueChars);
}
\ No newline at end of file
diff --git a/modules/util/src/main/java/com/gluonhq/attach/util/Platform.java b/modules/util/src/main/java/com/gluonhq/attach/util/Platform.java
index dd198ae8..668e5feb 100644
--- a/modules/util/src/main/java/com/gluonhq/attach/util/Platform.java
+++ b/modules/util/src/main/java/com/gluonhq/attach/util/Platform.java
@@ -65,18 +65,11 @@ public enum Platform {
private static final Logger LOGGER = Logger.getLogger(Platform.class.getName());
static {
- String s = System.getProperty("javafx.platform", null);
- if (s == null) {
- String os = System.getProperty("os.target", null);
- if (os != null) {
- LOGGER.info("javafx.platform is not defined, using: " + os + " from os.target");
- s = os;
- } else {
- LOGGER.severe("javafx.platform is not defined. Desktop will be assumed by default.");
- s = DESKTOP.getName();
- }
+ String os = System.getProperty("os.name", DESKTOP.getName()).toLowerCase(Locale.ROOT);
+ if (os.contains("mac") || os.contains("win") || os.contains("nux")) {
+ os = DESKTOP.getName();
}
- String name = s.toUpperCase(Locale.ROOT);
+ String name = os.toUpperCase(Locale.ROOT);
current = valueOf(name);
LOGGER.fine("Current platform: " + current);
}
diff --git a/modules/util/src/main/java/com/gluonhq/attach/util/impl/PropertyWatcher.java b/modules/util/src/main/java/com/gluonhq/attach/util/PropertyWatcher.java
similarity index 98%
rename from modules/util/src/main/java/com/gluonhq/attach/util/impl/PropertyWatcher.java
rename to modules/util/src/main/java/com/gluonhq/attach/util/PropertyWatcher.java
index 9a4e9b69..92c8dd5f 100644
--- a/modules/util/src/main/java/com/gluonhq/attach/util/impl/PropertyWatcher.java
+++ b/modules/util/src/main/java/com/gluonhq/attach/util/PropertyWatcher.java
@@ -25,7 +25,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.gluonhq.attach.util.impl;
+package com.gluonhq.attach.util;
import java.util.ArrayList;
import java.util.List;
diff --git a/modules/vibration/src/main/java/com/gluonhq/attach/vibration/impl/IOSVibrationService.java b/modules/vibration/src/main/java/com/gluonhq/attach/vibration/impl/IOSVibrationService.java
new file mode 100644
index 00000000..493759f8
--- /dev/null
+++ b/modules/vibration/src/main/java/com/gluonhq/attach/vibration/impl/IOSVibrationService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.vibration.impl;
+
+
+import com.gluonhq.attach.vibration.VibrationService;
+
+public class IOSVibrationService implements VibrationService {
+
+ static {
+ System.loadLibrary("Vibration");
+ }
+
+ @Override
+ public void vibrate() {
+ doVibrate();
+ }
+
+ @Override
+ public void vibrate(long... pattern) {
+ // pattern not supported on iOS
+ vibrate();
+ }
+
+ private native static void doVibrate();
+}
\ No newline at end of file
diff --git a/modules/vibration/src/main/java/com/gluonhq/attach/vibration/package-info.java b/modules/vibration/src/main/java/com/gluonhq/attach/vibration/package-info.java
index bc60a567..fba8eabe 100644
--- a/modules/vibration/src/main/java/com/gluonhq/attach/vibration/package-info.java
+++ b/modules/vibration/src/main/java/com/gluonhq/attach/vibration/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Vibration plugin,
+ * Primary API package for Attach - Vibration plugin,
* contains the interface {@link com.gluonhq.attach.vibration.VibrationService} and related classes.
*/
package com.gluonhq.attach.vibration;
\ No newline at end of file
diff --git a/modules/vibration/src/main/native/ios/Vibration.m b/modules/vibration/src/main/native/ios/Vibration.m
new file mode 100644
index 00000000..2df27dc3
--- /dev/null
+++ b/modules/vibration/src/main/native/ios/Vibration.m
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Vibration(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_vibration_impl_IOSVibrationService_doVibrate
+(JNIEnv *env, jclass jClass)
+{
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
diff --git a/modules/video/src/main/java/com/gluonhq/attach/video/impl/DefaultVideoService.java b/modules/video/src/main/java/com/gluonhq/attach/video/impl/DefaultVideoService.java
index b7984671..babf547b 100644
--- a/modules/video/src/main/java/com/gluonhq/attach/video/impl/DefaultVideoService.java
+++ b/modules/video/src/main/java/com/gluonhq/attach/video/impl/DefaultVideoService.java
@@ -72,7 +72,7 @@ public DefaultVideoService() {
assetsFolder = new File(Services.get(StorageService.class)
.flatMap(service -> service.getPrivateStorage())
- .orElseThrow(() -> new RuntimeException("Error accesing Private Storage folder")), "assets");
+ .orElseThrow(() -> new RuntimeException("Error accessing Private Storage folder")), "assets");
if (! assetsFolder.exists()) {
assetsFolder.mkdir();
diff --git a/modules/video/src/main/java/com/gluonhq/attach/video/impl/IOSVideoService.java b/modules/video/src/main/java/com/gluonhq/attach/video/impl/IOSVideoService.java
new file mode 100644
index 00000000..c62ed130
--- /dev/null
+++ b/modules/video/src/main/java/com/gluonhq/attach/video/impl/IOSVideoService.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2017 Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gluonhq.attach.video.impl;
+
+import javafx.application.Platform;
+import javafx.beans.Observable;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.geometry.Pos;
+import javafx.scene.media.MediaPlayer.Status;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class IOSVideoService extends DefaultVideoService {
+
+ static {
+ System.loadLibrary("Video");
+ initVideo();
+ }
+
+ private static final ReadOnlyObjectWrapper STATUS = new ReadOnlyObjectWrapper<>();
+ private static final BooleanProperty FULL_SCREEN = new SimpleBooleanProperty();
+ private static final IntegerProperty CURRENT_INDEX = new SimpleIntegerProperty();
+
+ public IOSVideoService() {
+ super();
+
+ if (debug) {
+ enableDebug();
+ }
+
+ playlist.addListener((Observable o) -> {
+ List list = new ArrayList<>();
+ for (String s : playlist) {
+ if (checkFileInResources(s)) {
+ File videoFile = getFileFromAssets(s);
+ list.add(videoFile.getAbsolutePath());
+ } else {
+ list.add(s);
+ }
+ }
+ setVideoPlaylist(list.toArray(new String[0]));
+ });
+
+ FULL_SCREEN.addListener((obs, ov, nv) -> setFullScreenMode(nv));
+ CURRENT_INDEX.addListener((obs, ov, nv) -> currentIndex(nv.intValue()));
+ }
+
+ @Override
+ public void show() {
+ showVideo();
+ }
+
+ @Override
+ public void play() {
+ playVideo();
+ }
+
+ @Override
+ public void stop() {
+ stopVideo();
+ }
+
+ @Override
+ public void pause() {
+ pauseVideo();
+ }
+
+ @Override
+ public void hide() {
+ hideVideo();
+ }
+
+ @Override
+ public void setPosition(Pos alignment, double topPadding, double rightPadding, double bottomPadding, double leftPadding) {
+ setPosition(alignment.getHpos().name(), alignment.getVpos().name(), topPadding, rightPadding, bottomPadding, leftPadding);
+ }
+
+ @Override
+ public void setLooping(boolean looping) {
+ looping(looping);
+ }
+
+ @Override
+ public void setControlsVisible(boolean controlsVisible) {
+ controlsVisible(controlsVisible);
+ }
+
+ @Override
+ public void setFullScreen(boolean fullScreen) {
+ FULL_SCREEN.set(fullScreen);
+ }
+
+ @Override
+ public BooleanProperty fullScreenProperty() {
+ return FULL_SCREEN;
+ }
+
+ @Override
+ public ReadOnlyObjectProperty statusProperty() {
+ return STATUS.getReadOnlyProperty();
+ }
+
+ @Override
+ public void setCurrentIndex(int index) {
+ CURRENT_INDEX.set(index);
+ }
+
+ @Override
+ public IntegerProperty currentIndexProperty() {
+ return CURRENT_INDEX;
+ }
+
+ // native
+ private static native void initVideo(); // init IDs for java callbacks from native
+ private native void setVideoPlaylist(String[] playlist);
+ private native void showVideo();
+ private native void playVideo();
+ private native void stopVideo();
+ private native void pauseVideo();
+ private native void hideVideo();
+ private native void looping(boolean looping);
+ private native void controlsVisible(boolean controlsVisible);
+ private native void currentIndex(int currentIndex);
+ private native void setFullScreenMode(boolean fullScreen);
+ private native void setPosition(String alignmentH, String alignmentV, double topPadding, double rightPadding, double bottomPadding, double leftPadding);
+ private static native void enableDebug();
+
+ // callbacks
+ private static void updateStatus(int value) {
+ Status s;
+ switch (value) {
+ case 0: s = Status.UNKNOWN; break;
+ case 1: s = Status.READY; break;
+ case 2: s = Status.PAUSED; break;
+ case 3: s = Status.PLAYING; break;
+ case 4: s = Status.STOPPED; break;
+ case 5: s = Status.DISPOSED; break;
+ default: s = Status.UNKNOWN;
+ }
+ Platform.runLater(() -> STATUS.set(s));
+ }
+
+ private static void updateFullScreen(boolean value) {
+ if (FULL_SCREEN.get() != value) {
+ Platform.runLater(() -> FULL_SCREEN.set(value));
+ }
+ }
+
+ private static void updateCurrentIndex(int index) {
+ if (CURRENT_INDEX.get() != index) {
+ Platform.runLater(() -> CURRENT_INDEX.set(index));
+ }
+ }
+}
diff --git a/modules/video/src/main/java/com/gluonhq/attach/video/package-info.java b/modules/video/src/main/java/com/gluonhq/attach/video/package-info.java
index 30b448ca..322cbd6f 100644
--- a/modules/video/src/main/java/com/gluonhq/attach/video/package-info.java
+++ b/modules/video/src/main/java/com/gluonhq/attach/video/package-info.java
@@ -27,7 +27,7 @@
*/
/**
- * Primary API package for Down - Video plugin,
+ * Primary API package for Attach - Video plugin,
* contains the interface {@link com.gluonhq.attach.video.VideoService} and related classes.
*/
package com.gluonhq.attach.video;
\ No newline at end of file
diff --git a/modules/video/src/main/native/ios/Video.h b/modules/video/src/main/native/ios/Video.h
new file mode 100644
index 00000000..24e195df
--- /dev/null
+++ b/modules/video/src/main/native/ios/Video.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#import
+#include "jni.h"
+#include "AttachMacros.h"
+#import
+#import
+
+typedef NS_ENUM(NSInteger, MediaPlayerStatus) {
+ MediaPlayerStatusUnknown,
+ MediaPlayerStatusReady,
+ MediaPlayerStatusPaused,
+ MediaPlayerStatusPlaying,
+ MediaPlayerStatusStopped,
+ MediaPlayerStatusDisposed
+};
+
+@interface Video :UIViewController
+{
+}
+ @property (nonatomic, strong) NSArray *arrayOfPlaylist;
+ @property (nonatomic, strong) AVPlayerViewController *avPlayerViewcontroller;
+ @property (nonatomic, strong) dispatch_semaphore_t semaphore;
+
+ - (void) initPlaylist:(NSArray *)playlist;
+ - (void) showVideo;
+ - (void) playVideo;
+ - (void) pauseVideo;
+ - (void) stopVideo;
+ - (void) hideVideo;
+ - (void) internalHide;
+ - (void) fullScreenVideo: (BOOL) value;
+ - (void) currentIndex: (int) index;
+ - (void) resizeRelocateVideo;
+@end
+
+void status(MediaPlayerStatus status);
+void updateFullScreen(BOOL value);
+void updateCurrentIndex(int index);
diff --git a/modules/video/src/main/native/ios/Video.m b/modules/video/src/main/native/ios/Video.m
new file mode 100644
index 00000000..bb5795a3
--- /dev/null
+++ b/modules/video/src/main/native/ios/Video.m
@@ -0,0 +1,778 @@
+/*
+ * Copyright (c) 2017, 2019, Gluon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "Video.h"
+
+JNIEnv *env;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad_Video(JavaVM *vm, void *reserved)
+{
+#ifdef JNI_VERSION_1_8
+ //min. returned JNI_VERSION required by JDK8 for builtin libraries
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
+ return JNI_VERSION_1_4;
+ }
+ return JNI_VERSION_1_8;
+#else
+ return JNI_VERSION_1_4;
+#endif
+}
+
+static int VideoInited = 0;
+
+// Video
+jclass mat_jVideoServiceClass;
+jmethodID mat_jVideoService_updateStatus = 0;
+jmethodID mat_jVideoService_updateFullScreen = 0;
+jmethodID mat_jVideoService_updateCurrentIndex = 0;
+
+Video *_video;
+UIView *_currentView;
+UIViewController *rootViewController;
+
+BOOL init;
+int currentMediaIndex = 0;
+NSString *videoName;
+NSURL *urlVideoFile;
+BOOL showing;
+MediaPlayerStatus videoStatus = (MediaPlayerStatus) MediaPlayerStatusUnknown;
+
+BOOL isVideo;
+BOOL loop;
+BOOL useControls;
+BOOL fullScreenMode;
+int alignH;
+int alignV;
+double topPadding, rightPadding, bottomPadding, leftPadding;
+BOOL debugVideo;
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_initVideo
+(JNIEnv *env, jclass jClass)
+{
+ if (VideoInited)
+ {
+ return;
+ }
+ VideoInited = 1;
+
+ mat_jVideoServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/video/impl/IOSVideoService"));
+ mat_jVideoService_updateStatus = (*env)->GetStaticMethodID(env, mat_jVideoServiceClass, "updateStatus", "(I)V");
+ mat_jVideoService_updateFullScreen = (*env)->GetStaticMethodID(env, mat_jVideoServiceClass, "updateFullScreen", "(Z)V");
+ mat_jVideoService_updateCurrentIndex = (*env)->GetStaticMethodID(env, mat_jVideoServiceClass, "updateCurrentIndex", "(I)V");
+
+ AttachLog(@"Init Video");
+ _video = [[Video alloc] init];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_setVideoPlaylist
+(JNIEnv *env, jclass jClass, jobjectArray jPlaylistArray)
+{
+ int playlistCount = (*env)->GetArrayLength(env, jPlaylistArray);
+ NSMutableArray *playItems = [[NSMutableArray alloc] init];
+
+ for (int i = 0; i < playlistCount; i++) {
+ jstring jplayItem = (jstring) ((*env)->GetObjectArrayElement(env, jPlaylistArray, i));
+ const jchar *playItemString = (*env)->GetStringChars(env, jplayItem, NULL);
+ NSString *playItem = [NSString stringWithCharacters:(UniChar *)playItemString length:(*env)->GetStringLength(env, jplayItem)];
+ (*env)->ReleaseStringChars(env, jplayItem, playItemString);
+ [playItems addObject:playItem];
+ }
+ if (debugVideo) {
+ AttachLog(@"Added video playlist with %lu items", (unsigned long)[playItems count]);
+ }
+ [_video initPlaylist:playItems];
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_showVideo
+(JNIEnv *env, jclass jClass, jstring jTitle)
+{
+ if (_video)
+ {
+ [_video showVideo];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_playVideo
+(JNIEnv *env, jclass jClass, jstring jTitle)
+{
+ if (_video)
+ {
+ [_video playVideo];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_pauseVideo
+(JNIEnv *env, jclass jClass)
+{
+ if (_video)
+ {
+ [_video pauseVideo];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_stopVideo
+(JNIEnv *env, jclass jClass)
+{
+ if (_video)
+ {
+ [_video stopVideo];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_hideVideo
+(JNIEnv *env, jclass jClass)
+{
+ if (_video)
+ {
+ [_video hideVideo];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_looping
+(JNIEnv *env, jclass jClass, jboolean jLooping)
+{
+ loop = jLooping;
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_controlsVisible
+(JNIEnv *env, jclass jClass, jboolean jControls)
+{
+ useControls = jControls;
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_setFullScreenMode
+(JNIEnv *env, jclass jClass, jboolean jfullscreen)
+{
+ if (_video)
+ {
+ [_video fullScreenVideo:jfullscreen];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_currentIndex
+(JNIEnv *env, jclass jClass, jint jindex)
+{
+ if (_video)
+ {
+ [_video currentIndex:jindex];
+ }
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_setPosition
+(JNIEnv *env, jclass jClass, jstring jalignmentH, jstring jalignmentV, jdouble jtopPadding,
+ jdouble jrightPadding, jdouble jbottomPadding, jdouble jleftPadding)
+{
+ const jchar *charsAlignH = (*env)->GetStringChars(env, jalignmentH, NULL);
+ NSString *sAlignH = [NSString stringWithCharacters:(UniChar *)charsAlignH length:(*env)->GetStringLength(env, jalignmentH)];
+ (*env)->ReleaseStringChars(env, jalignmentH, charsAlignH);
+
+ const jchar *charsAlignV = (*env)->GetStringChars(env, jalignmentV, NULL);
+ NSString *sAlignV = [NSString stringWithCharacters:(UniChar *)charsAlignV length:(*env)->GetStringLength(env, jalignmentV)];
+ (*env)->ReleaseStringChars(env, jalignmentV, charsAlignV);
+ if (debugVideo) {
+ AttachLog(@"Video Alignment H: %@, V: %@", sAlignH, sAlignV);
+ }
+
+ if ([sAlignH isEqualToString:@"LEFT"]) {
+ alignH = -1;
+ } else if ([sAlignH isEqualToString:@"RIGHT"]) {
+ alignH = 1;
+ } else {
+ alignH = 0;
+ }
+ if ([sAlignV isEqualToString:@"TOP"]) {
+ alignV = -1;
+ } else if ([sAlignV isEqualToString:@"BOTTOM"]) {
+ alignV = 1;
+ } else {
+ alignV = 0;
+ }
+ topPadding = jtopPadding;
+ rightPadding = jrightPadding;
+ bottomPadding = jbottomPadding;
+ leftPadding = jleftPadding;
+
+ [_video resizeRelocateVideo];
+ return;
+}
+
+JNIEXPORT void JNICALL Java_com_gluonhq_attach_video_impl_IOSVideoService_enableDebug
+(JNIEnv *env, jclass jClass)
+{
+ debugVideo = YES;
+}
+
+void status(MediaPlayerStatus status) {
+ videoStatus = status;
+ if (debugVideo) {
+ AttachLog(@"Media Player Status: %ld", (long) status);
+ }
+ (*env)->CallStaticVoidMethod(env, mat_jVideoServiceClass, mat_jVideoService_updateStatus, status);
+}
+
+void updateFullScreen(BOOL value) {
+ fullScreenMode = value;
+ (*env)->CallStaticVoidMethod(env, mat_jVideoServiceClass, mat_jVideoService_updateFullScreen, (value) ? JNI_TRUE : JNI_FALSE);
+}
+
+void updateCurrentIndex(int index) {
+ currentMediaIndex = index;
+ (*env)->CallStaticVoidMethod(env, mat_jVideoServiceClass, mat_jVideoService_updateCurrentIndex, index);
+}
+
+@implementation Video
+
+- (void) initPlaylist:(NSArray *)playlist
+{
+ if (_arrayOfPlaylist) {
+ [self logMessage:@"Update playlist"];
+ if ([playlist count] == 0) {
+ [self hideVideo];
+ } else if ([videoName length] > 0) {
+ if (! [playlist containsObject: videoName]) {
+ if (currentMediaIndex == 0) {
+ currentMediaIndex = -1;
+ }
+ [self logMessage:@"Update playlist to index 0"];
+ [self currentIndex:0];
+ } else {
+ NSUInteger index = [playlist indexOfObject:videoName];
+ if (index != currentMediaIndex) {
+ [self logMessage:@"Update playlist from index %d to new index %d", currentMediaIndex, index];
+ updateCurrentIndex(index);
+ }
+ }
+ }
+ }
+ _arrayOfPlaylist = [[NSArray alloc] initWithArray:playlist copyItems:YES];
+ [self logMessage:@"Init array %@", _arrayOfPlaylist];
+}
+
+- (void)initVideo
+{
+ [self logMessage:@"Init window"];
+ if(![[UIApplication sharedApplication] keyWindow])
+ {
+ AttachLog(@"key window was nil");
+ return;
+ }
+
+ NSArray *views = [[[UIApplication sharedApplication] keyWindow] subviews];
+ if(![views count]) {
+ AttachLog(@"views size was 0");
+ return;
+ }
+
+ _currentView = views[0];
+
+ rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
+ if(!rootViewController)
+ {
+ AttachLog(@"rootViewController was nil");
+ return;
+ }
+
+ init = YES;
+}
+
+- (void)showVideo
+{
+ if ([_arrayOfPlaylist count] == 0) {
+ AttachLog(@"There is no playlist available");
+ return;
+ }
+
+ if (! init) {
+ [_video initVideo];
+ if (! init) {
+ return;
+ }
+ }
+
+ if (showing) {
+ [self logMessage:@"Video layer was already added"];
+ return;
+ }
+
+ videoName = [_arrayOfPlaylist objectAtIndex:currentMediaIndex];
+ showing = YES;
+
+ if ([self prepareMedia]) {
+ [self logMessage:@"Video URL: %@", urlVideoFile.absoluteString];
+ [self setupVideo];
+ }
+ else {
+ [self logMessage:@"Invalid media file found, trying the next one"];
+ dispatch_semaphore_signal(_semaphore);
+ showing = NO;
+ status(MediaPlayerStatusUnknown);
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self currentIndex:currentMediaIndex + 1];
+ });
+ }
+}
+
+- (void)playVideo
+{
+ if ([_arrayOfPlaylist count] == 0) {
+ AttachLog(@"There is no playlist available");
+ return;
+ }
+
+ if (videoStatus == MediaPlayerStatusStopped || videoStatus == MediaPlayerStatusDisposed) {
+ // rewind
+ status(MediaPlayerStatusUnknown);
+ [self internalHide];
+ updateCurrentIndex(0);
+ }
+
+ if (! showing) {
+ _semaphore = dispatch_semaphore_create(0);
+ runOnMainQueueWithoutDeadlocking(^{
+ [self showVideo];
+ });
+ while (dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_NOW)) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
+ }
+ dispatch_release(_semaphore);
+
+ if (videoStatus == MediaPlayerStatusReady) {
+ [self logMessage:@"Video start playing [%d/%d]: %@", (currentMediaIndex + 1), [_arrayOfPlaylist count], videoName];
+ [_avPlayerViewcontroller.player play];
+ }
+ } else if (_avPlayerViewcontroller) {
+ [self logMessage:@"Video play"];
+ [_avPlayerViewcontroller.player play];
+ }
+}
+
+- (BOOL)prepareMedia
+{
+
+ if([[NSFileManager defaultManager] fileExistsAtPath:videoName]) {
+ [self logMessage:@"Video from resources"];
+ urlVideoFile = [[NSURL alloc] initFileURLWithPath:videoName];
+ return YES;
+ }
+ else if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:videoName]])
+ {
+ [self logMessage:@"Video from URL"];
+ urlVideoFile = [NSURL URLWithString:videoName];
+ return YES;
+ }
+ else
+ {
+ AttachLog(@"Error: %@ is not a valid name", videoName);
+ return NO;
+ }
+}
+
+- (void) setupVideo
+{
+ NSError* error = nil;
+
+ if(_avPlayerViewcontroller)
+ {
+ runOnMainQueueWithoutDeadlocking(^{
+ [self logMessage:@"Adding new item %@", urlVideoFile];
+ [_avPlayerViewcontroller.player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:urlVideoFile]];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoDidFinish:) name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[_avPlayerViewcontroller.player currentItem]];
+
+ [self resizeRelocateVideo];
+ status(MediaPlayerStatusReady);
+ [self logMessage:@"Video ready"];
+ if (_semaphore) {
+ dispatch_semaphore_signal(_semaphore);
+ }
+ });
+ }
+ else {
+ _avPlayerViewcontroller = [[AVPlayerViewController alloc] init];
+ _avPlayerViewcontroller.player = [AVPlayer playerWithURL:urlVideoFile];
+
+ if (! useControls) {
+ // a pinch gesture allows exiting full screen mode if embedded controls are not available
+ // When using embedded controls, a button is provided so the gesture is not required
+ _avPlayerViewcontroller.view.userInteractionEnabled = YES;
+ UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAvPlayer:)];
+ pinch.delegate = self;
+ NSString *ver = [[UIDevice currentDevice] systemVersion];
+ float ver_float = [ver floatValue];
+ if (ver_float < 8.0) {
+ [_avPlayerViewcontroller.view.subviews[0] addGestureRecognizer:pinch];
+ } else {
+ _avPlayerViewcontroller.contentOverlayView.gestureRecognizers = @[pinch];
+ }
+ }
+
+ [self resizeRelocateVideo];
+
+ [_currentView addSubview:_avPlayerViewcontroller.view];
+
+ if(!_avPlayerViewcontroller)
+ {
+ AttachLog(@"Error creating player: %@", error);
+ return;
+ }
+ _avPlayerViewcontroller.showsPlaybackControls = useControls;
+
+ [self logMessage:@"Adding listeners"];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoDidFinish:) name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[_avPlayerViewcontroller.player currentItem]];
+
+ [_avPlayerViewcontroller.player addObserver:self forKeyPath:@"status" options:0 context:nil];
+ [_avPlayerViewcontroller.player addObserver:self forKeyPath:@"rate" options:0 context:nil];
+ [_avPlayerViewcontroller.contentOverlayView addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
+ }
+ [self logMessage:@"Finished setupVideo"];
+}
+
+- (void)pauseVideo
+{
+ if(!_avPlayerViewcontroller)
+ {
+ return;
+ }
+ [self logMessage:@"Video pause"];
+ [_avPlayerViewcontroller.player pause];
+}
+
+- (void)stopVideo
+{
+ if(!_avPlayerViewcontroller)
+ {
+ return;
+ }
+ [self logMessage:@"Video stop"];
+ [_avPlayerViewcontroller.player pause];
+ status(MediaPlayerStatusStopped);
+}
+
+- (void)hideVideo
+{
+ [self internalHide];
+ [self dispose];
+}
+
+- (void)internalHide
+{
+ if (showing) {
+ @try {
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
+ } @catch (NSException *exception) {
+ AttachLog(@"Error removing NSNotificationCenter observer: %@", exception);
+ }
+ }
+ if (fullScreenMode) {
+ [self fullScreenVideo:NO];
+ }
+ AttachLog(@"AVPlayer hidden");
+ showing = NO;
+}
+
+- (void) fullScreenVideo: (BOOL) value
+{
+ if (value == fullScreenMode || ! isVideo) {
+ return;
+ }
+
+ if (useControls) {
+ AttachLog(@"Please, use the fullscreen button from the embedded controls");
+ updateFullScreen(false);
+ return;
+ }
+ fullScreenMode = value;
+ [UIView animateKeyframesWithDuration:0.3f
+ delay:0.0f
+ options:UIViewKeyframeAnimationOptionLayoutSubviews
+ animations:^{
+ [self resizeRelocateVideo];
+ }
+ completion:^(BOOL finished){
+ updateFullScreen(value);
+ }
+ ];
+}
+
+- (void) currentIndex: (int) index
+{
+ if (index == currentMediaIndex) {
+ return;
+ }
+ [self logMessage:@"Skipping current video from %d to %d", currentMediaIndex, index];
+
+ [self pauseVideo];
+ [self logMessage:@"Hiding current video file"];
+ [self internalHide];
+
+ if (0 <= index && index < [_arrayOfPlaylist count]) {
+ updateCurrentIndex(index);
+ [self logMessage:@"Showing new video file: %d", index];
+ [self playVideo];
+ } else if (loop) {
+ updateCurrentIndex(0);
+ [self logMessage:@"Showing first video file"];
+ [self playVideo];
+ } else {
+ [self logMessage:@"Disposing media player"];
+ [self dispose];
+ }
+}
+
+- (void) resizeRelocateVideo
+{
+ [self logMessage:@"Video resize and relocate"];
+ CGRect theLayerRect = [[UIScreen mainScreen] bounds];
+ if (fullScreenMode) {
+ [_avPlayerViewcontroller.view setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1]];
+ _avPlayerViewcontroller.view.frame = theLayerRect;
+ }
+ else
+ {
+ double maxW = theLayerRect.size.width - (leftPadding + rightPadding);
+ double maxH = theLayerRect.size.height - (topPadding + bottomPadding);
+ [self logMessage:@"Video max size: %f x %f", maxW, maxH];
+
+ if ([[_avPlayerViewcontroller.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
+ isVideo = true;
+ [_currentView bringSubviewToFront:_avPlayerViewcontroller.view];
+ AVAssetTrack *track = [_avPlayerViewcontroller.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo][0];
+ [self logMessage:@"Video track %@", track];
+
+ CGSize theNaturalSize = [track naturalSize];
+ theNaturalSize = CGSizeApplyAffineTransform(theNaturalSize, track.preferredTransform);
+ if (theNaturalSize.width == 0.0f || theNaturalSize.width == 0.0f) {
+ return;
+ }
+ theNaturalSize.width = fabs(theNaturalSize.width);
+ theNaturalSize.height = fabs(theNaturalSize.height);
+ [self logMessage:@"Video track natural size %@", NSStringFromCGSize(theNaturalSize)];
+
+ CGFloat movieAspectRatio = theNaturalSize.width / theNaturalSize.height;
+ CGFloat viewAspectRatio = maxW / maxH;
+ [self logMessage:@"Video movie ratio: %f, view ratio: %f", movieAspectRatio, viewAspectRatio];
+
+ CGRect theVideoRect = CGRectZero;
+ [self logMessage:@"Video set video rect: %@", NSStringFromCGRect(theVideoRect)];
+
+ if (viewAspectRatio < movieAspectRatio) {
+ theVideoRect.size.width = maxW;
+ theVideoRect.size.height = maxW / movieAspectRatio;
+ [self logMessage:@"Video video size %@", NSStringFromCGSize(theVideoRect.size)];
+ theVideoRect.origin.x = leftPadding;
+ if (alignV == -1) {
+ theVideoRect.origin.y = topPadding;
+ } else if (alignV == 0) {
+ theVideoRect.origin.y = topPadding + (maxH - theVideoRect.size.height) / 2;
+ } else {
+ theVideoRect.origin.y = topPadding + (maxH - theVideoRect.size.height);
+ }
+ } else {
+ theVideoRect.size.width = movieAspectRatio * maxH;
+ theVideoRect.size.height = maxH;
+ [self logMessage:@"Video video size %@", NSStringFromCGSize(theVideoRect.size)];
+ if (alignH == -1) {
+ theVideoRect.origin.x = leftPadding;
+ } else if (alignH == 0) {
+ theVideoRect.origin.x = leftPadding + (maxW - theVideoRect.size.width) / 2;
+ } else {
+ theVideoRect.origin.x = leftPadding + (maxW - theVideoRect.size.width);
+ }
+ theVideoRect.origin.y = topPadding;
+ }
+ [self logMessage:@"Video video origin %f x %f", theVideoRect.origin.x, theVideoRect.origin.y];
+
+ [self logMessage:@"Video frame: %@", NSStringFromCGRect(theVideoRect)];
+ [_avPlayerViewcontroller.view setBackgroundColor:[UIColor colorWithRed:1 green:1 blue:1 alpha:0]];
+ _avPlayerViewcontroller.view.frame = theVideoRect;
+ } else {
+ isVideo = false;
+ [_currentView sendSubviewToBack:_avPlayerViewcontroller.view];
+ if ([[_avPlayerViewcontroller.player.currentItem.asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
+ AVAssetTrack *track = [_avPlayerViewcontroller.player.currentItem.asset tracksWithMediaType:AVMediaTypeAudio][0];
+ [self logMessage:@"Audio track %@", track];
+ }
+ }
+ }
+}
+
+- (void)videoDidFinish:(NSNotification *)notification {
+ [self currentIndex:currentMediaIndex + 1];
+}
+
+- (void) dispose
+{
+ @try {
+ [_avPlayerViewcontroller.player removeObserver:self forKeyPath:@"status" context:nil];
+ } @catch (NSException *exception) {
+ AttachLog(@"Error removing player status observer: %@", exception);
+ }
+ @try {
+ [_avPlayerViewcontroller.player removeObserver:self forKeyPath:@"rate" context:nil];
+ } @catch (NSException *exception) {
+ AttachLog(@"Error removing player rate observer: %@", exception);
+ }
+ @try {
+ [_avPlayerViewcontroller.contentOverlayView removeObserver:self forKeyPath:@"bounds" context:nil];
+ } @catch (NSException *exception) {
+ AttachLog(@"Error removing contentOverlayView observer: %@", exception);
+ }
+ @try {
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
+ } @catch (NSException *exception) {
+ AttachLog(@"Error removing orientation observer: %@", exception);
+ }
+ [_avPlayerViewcontroller.player replaceCurrentItemWithPlayerItem:nil];
+ [_avPlayerViewcontroller dismissViewControllerAnimated:YES completion:nil];
+ [_avPlayerViewcontroller.view removeFromSuperview];
+ [_avPlayerViewcontroller release];
+ _avPlayerViewcontroller = nil;
+ status(MediaPlayerStatusDisposed);
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (object == _avPlayerViewcontroller.player && [keyPath isEqualToString:@"status"])
+ {
+ if (_avPlayerViewcontroller.player.status == AVPlayerStatusFailed) {
+ AttachLog(@"AVPlayer Failed");
+ status(MediaPlayerStatusUnknown);
+ } else if (_avPlayerViewcontroller.player.status == AVPlayerStatusReadyToPlay) {
+ AttachLog(@"AVPlayerStatusReadyToPlay");
+ status(MediaPlayerStatusReady);
+ [self logMessage:@"Video ready"];
+ } else if (_avPlayerViewcontroller.player.status == AVPlayerItemStatusUnknown) {
+ AttachLog(@"AVPlayer Unknown");
+ status(MediaPlayerStatusUnknown);
+ }
+ if (_semaphore) {
+ dispatch_semaphore_signal(_semaphore);
+ }
+ }
+ else if (object == _avPlayerViewcontroller.player && [keyPath isEqualToString:@"rate"])
+ {
+ if ([_avPlayerViewcontroller.player rate]) {
+ status(MediaPlayerStatusPlaying); // This changes the button to Pause
+ }
+ else {
+ status(MediaPlayerStatusPaused); // This changes the button to Play
+ }
+ }
+ else if (object == _avPlayerViewcontroller.contentOverlayView && [keyPath isEqualToString:@"bounds"])
+ {
+ CGRect oldBounds = [change[NSKeyValueChangeOldKey] CGRectValue];
+ CGRect newBounds = [change[NSKeyValueChangeNewKey] CGRectValue];
+ BOOL wasFullscreen = CGRectEqualToRect(oldBounds, [UIScreen mainScreen].bounds);
+ BOOL isFullscreen = CGRectEqualToRect(newBounds, [UIScreen mainScreen].bounds);
+ if (isFullscreen && !wasFullscreen)
+ {
+ if (CGRectEqualToRect(oldBounds, CGRectMake(0, 0, newBounds.size.height, newBounds.size.width)))
+ {
+ [self logMessage:@"Video rotated fullscreen"];
+ return;
+ }
+ else
+ {
+ [self logMessage:@"Video entered fullscreen"];
+ }
+ fullScreenMode = YES;
+ }
+ else if (!isFullscreen && wasFullscreen)
+ {
+ [self logMessage:@"Video exited fullscreen"];
+ fullScreenMode = NO;
+ }
+
+ if (useControls && ((isFullscreen && !wasFullscreen) || (!isFullscreen && wasFullscreen))) {
+ // workaround to avoid a bug in one of the subviews constraints.
+
+ CMTime currentTime = _avPlayerViewcontroller.player.currentTime;
+ [_avPlayerViewcontroller.player seekToTime:CMTimeMake(0, 1)];
+
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC));
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
+ updateFullScreen(isFullscreen);
+ [_avPlayerViewcontroller.player seekToTime:currentTime];
+ [_avPlayerViewcontroller.player play];
+ });
+ }
+ }
+}
+
+- (void)pinchAvPlayer:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
+ UIGestureRecognizerState state = [pinchGestureRecognizer state];
+
+ if (state == UIGestureRecognizerStateEnded)
+ {
+ CGFloat scale = [pinchGestureRecognizer scale];
+ [pinchGestureRecognizer setScale:1.0];
+ if ((fullScreenMode && scale < 1.0) || (!fullScreenMode && scale > 1)) {
+ [self fullScreenVideo:! fullScreenMode];
+ }
+ }
+}
+
+void runOnMainQueueWithoutDeadlocking(void (^block)(void))
+{
+ if ([NSThread isMainThread])
+ {
+ block();
+ }
+ else
+ {
+ dispatch_sync(dispatch_get_main_queue(), block);
+ }
+}
+
+-(void)OrientationDidChange:(NSNotification*)notification
+{
+ [self logMessage:@"OrientationDidChange, resizing"];
+ [self resizeRelocateVideo];
+}
+
+- (void) logMessage:(NSString *)format, ...;
+{
+ if (debugVideo)
+ {
+ va_list args;
+ va_start(args, format);
+ NSLogv([@"[Debug] " stringByAppendingString:format], args);
+ va_end(args);
+ }
+}
+@end
\ No newline at end of file