Create common JNI module for android
authorPraveen Naik S <praveen.s@samsung.com>
Wed, 21 Sep 2022 07:16:02 +0000 (12:46 +0530)
committerYoungjae Shin <yj99.shin@samsung.com>
Fri, 23 Sep 2022 05:52:47 +0000 (14:52 +0900)
16 files changed:
android/aitt-native/.gitignore [new file with mode: 0644]
android/aitt-native/CMakeLists.txt [moved from android/aitt/CMakeLists.txt with 95% similarity]
android/aitt-native/build.gradle [new file with mode: 0644]
android/aitt-native/consumer-rules.pro [new file with mode: 0644]
android/aitt-native/proguard-rules.pro [new file with mode: 0644]
android/aitt-native/src/androidTest/java/com/example/aittnative/ExampleInstrumentedTest.java [new file with mode: 0644]
android/aitt-native/src/main/AndroidManifest.xml [new file with mode: 0644]
android/aitt-native/src/main/java/com/samsung/android/aittnative/JniInterface.java [new file with mode: 0644]
android/aitt-native/src/main/jni/aitt_jni.cc [moved from android/aitt/src/main/jni/aitt_jni.cc with 98% similarity]
android/aitt-native/src/main/jni/aitt_jni.h [moved from android/aitt/src/main/jni/aitt_jni.h with 100% similarity]
android/aitt-native/src/test/java/com/example/aittnative/ExampleUnitTest.java [new file with mode: 0644]
android/aitt/build.gradle
android/aitt/src/main/java/com/samsung/android/aitt/Aitt.java
android/aitt/src/test/java/com/samsung/android/aitt/AittUnitTest.java
android/aitt/src/test/java/com/samsung/android/aitt/ShadowJniInterface.java [new file with mode: 0644]
settings.gradle

diff --git a/android/aitt-native/.gitignore b/android/aitt-native/.gitignore
new file mode 100644 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
similarity index 95%
rename from android/aitt/CMakeLists.txt
rename to android/aitt-native/CMakeLists.txt
index 4d1b82f..6268d52 100644 (file)
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.4.1)
-project("aitt-android" CXX)
+project("aitt-native" CXX)
 
 if(NOT DEFINED PROJECT_ROOT_DIR)
     set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
diff --git a/android/aitt-native/build.gradle b/android/aitt-native/build.gradle
new file mode 100644 (file)
index 0000000..5f94ff0
--- /dev/null
@@ -0,0 +1,130 @@
+plugins {
+    id 'com.android.library'
+    id "de.undercouch.download" version "5.0.1"
+}
+
+def thirdPartyDir = new File ("${rootProject.projectDir}/third_party")
+
+def flatbuffersDir = new File("${thirdPartyDir}/flatbuffers-2.0.0")
+def mosquittoDir = new File("${thirdPartyDir}/mosquitto-2.0.14")
+
+android {
+    compileSdkVersion 33
+    ndkVersion "21.3.6528147"
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion 33
+        versionCode 1
+        versionName '1.0'
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+        externalNativeBuild {
+            cmake {
+                arguments '-DLOG_STDOUT=OFF'
+                arguments '-DCMAKE_VERBOSE_MAKEFILE=1'
+                arguments '-DCMAKE_INSTALL_PREFIX:PATH=/usr'
+                arguments '-DANDROID_STL=c++_shared'
+                arguments "-DANDROID_NDK_HOME=${System.env.ANDROID_NDK_ROOT}"
+                arguments "-DGSTREAMER_ROOT_ANDROID=${System.env.GSTREAMER_ROOT_ANDROID}"
+                arguments '-DBUILD_TESTING=OFF'
+                arguments '-DUSE_PREBUILT=OFF'
+                arguments '-DVERSIONING=OFF'
+                arguments '-DPLATFORM=android'
+                arguments '-DCOVERAGE=OFF'
+                abiFilters 'arm64-v8a', 'x86'
+                cppFlags "-std=c++17"
+                targets "aitt-native", "aitt-transport-tcp"
+            }
+        }
+    }
+
+    externalNativeBuild {
+        cmake {
+            path file('./CMakeLists.txt')
+        }
+    }
+
+    buildFeatures {
+        prefab true
+    }
+
+    packagingOptions {
+        jniLibs {
+            useLegacyPackaging = true
+        }
+        pickFirst 'lib/arm64-v8a/libaitt-common.so'
+    }
+    libraryVariants.all { variant ->
+        variant.outputs.all {
+            outputFileName = "${archivesBaseName}-${defaultConfig.versionName}.aar"
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+
+    compileOnly project(":android:flatbuffers")
+    compileOnly project(":android:mosquitto")
+    implementation 'com.google.flatbuffers:flatbuffers-java:2.0.0'
+    implementation 'com.android.ndk.thirdparty:openssl:1.1.1g-alpha-1'
+
+    implementation 'androidx.appcompat:appcompat:1.5.0'
+    implementation 'com.google.android.material:material:1.6.1'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
+
+task downloadFlatBuffers(type: Download) {
+    doFirst {
+        println("Downloading FlatBuffers")
+    }
+    src "https://github.com/google/flatbuffers/archive/refs/tags/v2.0.0.zip"
+    dest new File(thirdPartyDir, "flatbuffers.zip")
+    onlyIfModified true
+}
+
+task unzipFlatBuffers(type: Copy, dependsOn: downloadFlatBuffers) {
+    doFirst {
+        println("Unzipping FlatBuffers")
+    }
+    from zipTree(downloadFlatBuffers.dest)
+    into thirdPartyDir
+    onlyIf { !flatbuffersDir.exists() }
+}
+
+task downloadMosquitto(type: Download) {
+    doFirst {
+        println("Downloading Mosquitto")
+    }
+    src "https://github.com/eclipse/mosquitto/archive/refs/tags/v2.0.14.zip"
+    dest new File(thirdPartyDir, "mosquitto-2.0.14.zip")
+    onlyIfModified true
+}
+
+task unzipMosquitto(type: Copy, dependsOn: downloadMosquitto) {
+    doFirst {
+        println("Unzipping Mosquitto")
+    }
+    from zipTree(downloadMosquitto.dest)
+    into thirdPartyDir
+    onlyIf { !mosquittoDir.exists() }
+}
+
+preBuild.dependsOn(unzipFlatBuffers)
+preBuild.dependsOn(unzipMosquitto)
+preBuild.dependsOn ":android:flatbuffers:build"
+preBuild.dependsOn ":android:mosquitto:build"
diff --git a/android/aitt-native/consumer-rules.pro b/android/aitt-native/consumer-rules.pro
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/android/aitt-native/proguard-rules.pro b/android/aitt-native/proguard-rules.pro
new file mode 100644 (file)
index 0000000..f1b4245
--- /dev/null
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/aitt-native/src/androidTest/java/com/example/aittnative/ExampleInstrumentedTest.java b/android/aitt-native/src/androidTest/java/com/example/aittnative/ExampleInstrumentedTest.java
new file mode 100644 (file)
index 0000000..89f7431
--- /dev/null
@@ -0,0 +1,26 @@
+package com.example.aittnative;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.example.aittnative.test", appContext.getPackageName());
+    }
+}
diff --git a/android/aitt-native/src/main/AndroidManifest.xml b/android/aitt-native/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..6aee99f
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.samsung.android.aittnative">
+
+</manifest>
diff --git a/android/aitt-native/src/main/java/com/samsung/android/aittnative/JniInterface.java b/android/aitt-native/src/main/java/com/samsung/android/aittnative/JniInterface.java
new file mode 100644 (file)
index 0000000..c95c56a
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.samsung.android.aittnative;
+
+/**
+ * Jni Interface class as intermediate layer for android aitt and other transport modules to interact with JNI module.
+ */
+public class JniInterface {
+
+    private JniCallback jniCallback;
+    private JniConnectionCallback jniConnectionCallback;
+    private long instance = 0;
+
+    /**
+     * Load aitt-android library
+     */
+    static {
+        try {
+            System.loadLibrary("aitt-native");
+        } catch (UnsatisfiedLinkError e) {
+            // only ignore exception in non-android env
+            if ("Dalvik".equals(System.getProperty("java.vm.name"))) throw e;
+        }
+    }
+
+    /**
+     * JNI callback interface to send data from JNI layer to Java layer(aitt or transport module)
+     */
+    public interface JniCallback {
+        void jniDataPush(String topic, byte[] data);
+    }
+
+    /**
+     * JNI callback interface to send connection callback status to aitt layer
+     */
+    public interface JniConnectionCallback {
+        void jniConnectionCB(int status);
+    }
+
+    /**
+     * JNI interface constructor
+     */
+    public JniInterface() {
+
+    }
+
+    /**
+     * JNI interface API to initialize JNI module
+     * @param id unique mqtt id
+     * @param ip self IP address of device
+     * @param clearSession to clear current session if client disconnects
+     * @return returns the JNI instance object in long
+     */
+    public long init(String id, String ip, boolean clearSession) {
+        instance = initJNI(id, ip, clearSession);
+        return instance;
+    }
+
+    /**
+     * JNI Interface API to connect to MQTT broker
+     * @param brokerIp mqtt broker ip address
+     * @param port mqtt broker port number
+     */
+    public void connect(String brokerIp, int port) {
+        connectJNI(instance, brokerIp, port);
+    }
+
+    /**
+     * JNI Interface API to subscribe to a topic
+     * @param topic String to which applications can subscribe, to receive data
+     * @param protocol Protocol supported by application, invoking subscribe
+     * @param qos QoS at which the message should be delivered
+     * @return returns the subscribe instance in long
+     */
+    public long subscribe(final String topic, int protocol, int qos) {
+        return subscribeJNI(instance, topic, protocol, qos);
+    }
+
+    /**
+     * JNI Interface API to disconnect from broker
+     */
+    public void disconnect() {
+        disconnectJNI(instance);
+    }
+
+    /**
+     * JNI Interface API to publish data to specified topic
+     * @param topic String to which message needs to be published
+     * @param data Byte message to be published
+     * @param datalen Size/length of the message to be published
+     * @param protocol Protocol to be used to publish message
+     * @param qos QoS at which the message should be delivered
+     * @param retain Boolean to decide whether or not the message should be retained by the broker
+     */
+    public void publish(final String topic, final byte[] data, long datalen, int protocol, int qos, boolean retain) {
+        publishJNI(instance, topic, data, datalen, protocol, qos, retain);
+    }
+
+    /**
+     * JNI Interface API to unsubscribe the given topic
+     * @param aittSubId Subscribe ID of the topics to be unsubscribed
+     */
+    public void unsubscribe(final long aittSubId) {
+        unsubscribeJNI(instance, aittSubId);
+    }
+
+    /**
+     * JNI Interface API to set connection callback instance
+     * @param cb callback instance of JniConnectionCallback interface
+     */
+    public void setConnectionCallback(JniConnectionCallback cb) {
+        jniConnectionCallback = cb;
+        setConnectionCallbackJNI(instance);
+    }
+
+    /**
+     * JNI Interface API to register jni callback instance
+     * @param callBack callback instance of JniCallback interface
+     */
+    public void registerJniCallback(JniCallback callBack) {
+        jniCallback = callBack;
+    }
+
+    /**
+     * messageCallback API to receive data from JNI layer to JNI interface layer
+     * @param topic Topic to which data is received
+     * @param payload Data that is sent from JNI to JNI interface layer
+     */
+    private void messageCallback(String topic, byte[] payload) {
+        jniCallback.jniDataPush(topic, payload);
+    }
+
+    /**
+     * connectionStatusCallback API to receive connection status from JNI to JNI interface layer
+     * @param status status of the device connection with mqtt broker
+     */
+    private void connectionStatusCallback(int status) {
+        if (jniConnectionCallback != null) {
+            jniConnectionCallback.jniConnectionCB(status);
+        }
+    }
+
+    /* native API's set */
+    /* Native API to initialize JNI */
+    private native long initJNI(String id, String ip, boolean clearSession);
+
+    /* Native API for connecting to broker */
+    private native void connectJNI(long instance, final String host, int port);
+
+    /* Native API for disconnecting from broker */
+    private native void disconnectJNI(long instance);
+
+    /* Native API for setting connection callback */
+    private native void setConnectionCallbackJNI(long instance);
+
+    /* Native API for publishing to a topic */
+    private native void publishJNI(long instance, final String topic, final byte[] data, long datalen, int protocol, int qos, boolean retain);
+
+    /* Native API for subscribing to a topic */
+    private native long subscribeJNI(long instance, final String topic, int protocol, int qos);
+
+    /* Native API for unsubscribing a topic */
+    private native void unsubscribeJNI(long instance, final long aittSubId);
+}
similarity index 98%
rename from android/aitt/src/main/jni/aitt_jni.cc
rename to android/aitt-native/src/main/jni/aitt_jni.cc
index 80fda25..d537ef2 100644 (file)
@@ -354,7 +354,7 @@ void AittNativeInterface::setConnectionCallback(JNIEnv *env,
  * @param jniInterfaceObject JNI interface object
  * @param id unique mqtt id
  * @param ip self IP address of device
- * @param clearSession "to clear current session if client disconnects
+ * @param clearSession to clear current session if client disconnects
  * @return returns the aitt interface object in long
  */
 jlong AittNativeInterface::init(JNIEnv *env,
@@ -394,7 +394,7 @@ jlong AittNativeInterface::init(JNIEnv *env,
     try {
         instance->cbObject = env->NewGlobalRef(jniInterfaceObject);
 
-        jclass callbackClass = env->FindClass("com/samsung/android/aitt/Aitt");
+        jclass callbackClass = env->FindClass("com/samsung/android/aittnative/JniInterface");
         cbContext.messageCallbackMethodID =
                 env->GetMethodID(callbackClass, "messageCallback", "(Ljava/lang/String;[B)V");
         cbContext.connectionCallbackMethodID =
@@ -422,7 +422,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
         JNI_LOG(ANDROID_LOG_ERROR, TAG, "Not a valid JNI version");
         return JNI_ERR;
     }
-    jclass klass = env->FindClass("com/samsung/android/aitt/Aitt");
+    jclass klass = env->FindClass("com/samsung/android/aittnative/JniInterface");
     if (nullptr == klass) {
         JNI_LOG(ANDROID_LOG_ERROR, TAG, "klass is null");
         return JNI_ERR;
diff --git a/android/aitt-native/src/test/java/com/example/aittnative/ExampleUnitTest.java b/android/aitt-native/src/test/java/com/example/aittnative/ExampleUnitTest.java
new file mode 100644 (file)
index 0000000..741f2ba
--- /dev/null
@@ -0,0 +1,17 @@
+package com.example.aittnative;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}
index 1f56b8b..348c0d7 100644 (file)
@@ -1,16 +1,9 @@
 plugins {
     id 'com.android.library'
-    id "de.undercouch.download" version "5.0.1"
 }
 
-def thirdPartyDir = new File("${rootProject.projectDir}/third_party")
-
-def flatbuffersDir = new File("${thirdPartyDir}/flatbuffers-2.0.0")
-def mosquittoDir = new File("${thirdPartyDir}/mosquitto-2.0.14")
-
 android {
-    compileSdkVersion 31
-    ndkVersion "21.3.6528147"
+    compileSdkVersion 32
     defaultConfig {
         minSdkVersion 28
         targetSdkVersion 31
@@ -18,34 +11,8 @@ android {
         versionName '1.0'
         project.archivesBaseName = "aitt"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        externalNativeBuild {
-            cmake {
-                arguments '-DLOG_STDOUT=OFF'
-                arguments '-DCMAKE_VERBOSE_MAKEFILE=1'
-                arguments '-DCMAKE_INSTALL_PREFIX:PATH=/usr'
-                arguments '-DANDROID_STL=c++_shared'
-                arguments "-DANDROID_NDK_HOME=${System.env.ANDROID_NDK_ROOT}"
-                arguments "-DGSTREAMER_ROOT_ANDROID=${System.env.GSTREAMER_ROOT_ANDROID}"
-                arguments '-DBUILD_TESTING=OFF'
-                arguments '-DUSE_PREBUILT=OFF'
-                arguments '-DVERSIONING=OFF'
-                arguments '-DPLATFORM=android'
-                arguments '-DCOVERAGE=OFF'
-                abiFilters 'arm64-v8a', 'x86'
-                cppFlags "-std=c++17"
-                targets "aitt-android", "aitt-transport-tcp"
-            }
-        }
     }
 
-    externalNativeBuild {
-        cmake {
-            path file('./CMakeLists.txt')
-        }
-    }
-    buildFeatures {
-        prefab true
-    }
     buildTypes {
         debug {
             debuggable true
@@ -59,12 +26,7 @@ android {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8
     }
-    packagingOptions {
-        jniLibs {
-            useLegacyPackaging = true
-        }
-        pickFirst 'lib/armeabi-v7a/libaitt.so'
-    }
+
     libraryVariants.all { variant ->
         variant.outputs.all {
             outputFileName = "${archivesBaseName}-${defaultConfig.versionName}.aar"
@@ -79,72 +41,26 @@ android {
                 includeNoLocationClasses = true
                 excludes = ['jdk.internal.*']
             }
+            jvmArgs '-noverify'
         }
     }
 }
 
 dependencies {
-    compileOnly project(":android:flatbuffers")
-    compileOnly project(":android:mosquitto")
-
     implementation 'androidx.appcompat:appcompat:1.4.1'
     implementation 'com.google.flatbuffers:flatbuffers-java:2.0.0'
-    implementation 'com.android.ndk.thirdparty:openssl:1.1.1g-alpha-1'
-
 
     implementation project(path: ':android:modules:tcp')
     implementation project(path: ':android:modules:webrtc')
+    implementation project(path: ':android:aitt-native')
 
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.mockito:mockito-core:2.25.0'
-    testImplementation 'org.powermock:powermock-core:2.0.0-beta.5'
-    testImplementation 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
-    testImplementation 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
+    testImplementation 'org.robolectric:robolectric:4.8.1'
 
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 }
 
-task downloadFlatBuffers(type: Download) {
-    doFirst {
-        println("Downloading FlatBuffers")
-    }
-    src "https://github.com/google/flatbuffers/archive/refs/tags/v2.0.0.zip"
-    dest new File(thirdPartyDir, "flatbuffers.zip")
-    onlyIfModified true
-}
-
-task unzipFlatBuffers(type: Copy, dependsOn: downloadFlatBuffers) {
-    doFirst {
-        println("Unzipping FlatBuffers")
-    }
-    from zipTree(downloadFlatBuffers.dest)
-    into thirdPartyDir
-    onlyIf { !flatbuffersDir.exists() }
-}
-
-task downloadMosquitto(type: Download) {
-    doFirst {
-        println("Downloading Mosquitto")
-    }
-    src "https://github.com/eclipse/mosquitto/archive/refs/tags/v2.0.14.zip"
-    dest new File(thirdPartyDir, "mosquitto-2.0.14.zip")
-    onlyIfModified true
-}
-
-task unzipMosquitto(type: Copy, dependsOn: downloadMosquitto) {
-    doFirst {
-        println("Unzipping Mosquitto")
-    }
-    from zipTree(downloadMosquitto.dest)
-    into thirdPartyDir
-    onlyIf { !mosquittoDir.exists() }
-}
-
-preBuild.dependsOn(unzipFlatBuffers)
-preBuild.dependsOn(unzipMosquitto)
-preBuild.dependsOn ":android:flatbuffers:build"
-preBuild.dependsOn ":android:mosquitto:build"
-
 apply plugin: 'jacoco'
 
 task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
index b2698bd..0af96ee 100644 (file)
@@ -22,6 +22,7 @@ import android.util.Pair;
 import androidx.annotation.Nullable;
 
 import com.google.flatbuffers.FlexBuffers;
+import com.samsung.android.aittnative.JniInterface;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -42,22 +43,12 @@ public class Aitt {
     private static final String TAG = "AITT_ANDROID";
     private static final String INVALID_TOPIC = "Invalid topic";
 
-    /**
-     * Load aitt-android library
-     */
-    static {
-        try {
-            System.loadLibrary("aitt-android");
-        }catch (UnsatisfiedLinkError e){
-            // only ignore exception in non-android env
-            if ("Dalvik".equals(System.getProperty("java.vm.name"))) throw e;
-        }
-    }
     private Map<String, ArrayList<SubscribeCallback>> subscribeCallbacks = new HashMap<>();
     private Map<String, HostTable> publishTable = new HashMap<>();
     private Map<String, Pair<Protocol, Object>> subscribeMap = new HashMap<>();
     private Map<String, Long> aittSubId = new HashMap<>();
     private ConnectionCallback connectionCallback = null;
+    private JniInterface mJniInterface = new JniInterface();
 
     private long instance = 0;
     private String ip;
@@ -156,12 +147,18 @@ public class Aitt {
         if (id == null || id.isEmpty()) {
             throw new IllegalArgumentException("Invalid id");
         }
-        instance = initJNI(id, ip, clearSession);
+        instance = mJniInterface.init(id, ip, clearSession);
         if (instance == 0L) {
             throw new InstantiationException("Failed to instantiate native instance");
         }
         this.ip = ip;
         this.appContext = appContext;
+        mJniInterface.registerJniCallback(new JniInterface.JniCallback(){
+            @Override
+            public void jniDataPush(String _topic, byte[] payload) {
+                messageCallback(_topic, payload);
+            }
+        });
     }
 
     /**
@@ -173,7 +170,12 @@ public class Aitt {
             throw new IllegalArgumentException("Invalid callback");
         }
         connectionCallback = callback;
-        setConnectionCallbackJNI(instance);
+        mJniInterface.setConnectionCallback(new JniInterface.JniConnectionCallback(){
+            @Override
+            public void jniConnectionCB(int status) {
+                connectionStatusCallback(status);
+            }
+        });
     }
 
     /**
@@ -193,18 +195,18 @@ public class Aitt {
         if (brokerIp == null || brokerIp.isEmpty()) {
             brokerIp = Definitions.AITT_LOCALHOST;
         }
-        connectJNI(instance, brokerIp, port);
+        mJniInterface.connect(brokerIp, port);
         //Subscribe to java discovery topic
-        subscribeJNI(instance, Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, Protocol.MQTT.getValue(), QoS.EXACTLY_ONCE.ordinal());
+        mJniInterface.subscribe(Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, Protocol.MQTT.getValue(), QoS.EXACTLY_ONCE.ordinal());
     }
 
     /**
      * Method to disconnect from MQTT broker
      */
     public void disconnect() {
-        publishJNI(instance, Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, new byte[0], 0, Protocol.MQTT.getValue(), QoS.AT_LEAST_ONCE.ordinal(), true);
+        mJniInterface.publish(Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, new byte[0], 0, Protocol.MQTT.getValue(), QoS.AT_LEAST_ONCE.ordinal(), true);
 
-        disconnectJNI(instance);
+        mJniInterface.disconnect();
         try {
             close();
         } catch (Exception e) {
@@ -256,7 +258,7 @@ public class Aitt {
         }
 
         if(jniProtocols > 0) {
-            publishJNI(instance, topic, message, message.length, jniProtocols, qos.ordinal(), retain);
+            mJniInterface.publish(topic, message, message.length, jniProtocols, qos.ordinal(), retain);
         }
 
         for(Protocol pro : protocols) {
@@ -364,7 +366,7 @@ public class Aitt {
         }
 
         if(jniProtocols > 0) {
-            Long pObject = subscribeJNI(instance, topic, jniProtocols, qos.ordinal());
+            Long pObject = mJniInterface.subscribe(topic, jniProtocols, qos.ordinal());
             synchronized (this) {
                 aittSubId.put(topic, pObject);
             }
@@ -387,7 +389,7 @@ public class Aitt {
                         messageReceived(message);
                     });
                     byte[] data = transportHandler.getPublishData();
-                    publishJNI(instance, Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, data, data.length, Protocol.MQTT.value, QoS.EXACTLY_ONCE.ordinal(), true);
+                    mJniInterface.publish(Definitions.JAVA_SPECIFIC_DISCOVERY_TOPIC, data, data.length, Protocol.MQTT.value, QoS.EXACTLY_ONCE.ordinal(), true);
                 }
             } catch (Exception e) {
                 Log.e(TAG, "Error during subscribe", e);
@@ -466,7 +468,7 @@ public class Aitt {
                     }
                 }
                 if (paittSubId != null) {
-                    unsubscribeJNI(instance, paittSubId);
+                    mJniInterface.unsubscribe(paittSubId);
                 }
             }
 
@@ -487,7 +489,6 @@ public class Aitt {
      *   2: MQTT connection failed
      */
     private void connectionStatusCallback(int status) {
-
         switch (status) {
             case 0:
                 connectionCallback.onDisconnected();
@@ -665,25 +666,4 @@ public class Aitt {
         }
     }
 
-    /* native API's set */
-    /* Native API to initialize JNI */
-    private native long initJNI(String id, String ip, boolean clearSession);
-
-    /* Native API for connecting to broker */
-    private native void connectJNI(long instance, final String host, int port);
-
-    /* Native API for disconnecting from broker */
-    private native void disconnectJNI(long instance);
-
-    /* Native API for setting connection callback */
-    private native void setConnectionCallbackJNI(long instance);
-
-    /* Native API for publishing to a topic */
-    private native void publishJNI(long instance, final String topic, final byte[] data, long datalen, int protocol, int qos, boolean retain);
-
-    /* Native API for subscribing to a topic */
-    private native long subscribeJNI(long instance, final String topic, int protocol, int qos);
-
-    /* Native API for unsubscribing a topic */
-    private native void unsubscribeJNI(long instance, final long aittSubId);
 }
index 09f15ee..9123f4b 100644 (file)
@@ -29,10 +29,8 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.api.support.membermodification.MemberMatcher;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
@@ -40,8 +38,8 @@ import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.EnumSet;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(Aitt.class)
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowJniInterface.class)
 public class AittUnitTest {
    @Mock
    private final Context appContext = mock(Context.class);
@@ -53,58 +51,17 @@ public class AittUnitTest {
    private final String message = "test message";
    private final String aittId = "aitt";
 
+   ShadowJniInterface shadowJniInterface = new ShadowJniInterface();
+
    private Method messageCallbackMethod;
 
    @Before
    public void initialize() {
       try {
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "initJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return 1L;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "connectJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return null;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "disconnectJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return null;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "setConnectionCallbackJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return null;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "publishJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return null;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "subscribeJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return 1L;
-            }
-         });
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "unsubscribeJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return null;
-            }
-         });
-
          messageCallbackMethod = Aitt.class.getDeclaredMethod("messageCallback", String.class, byte[].class);
          messageCallbackMethod.setAccessible(true);
       } catch(Exception e) {
-         fail("Failed to mock Aitt " + e);
+         fail("Failed to Initialize " + e);
       }
    }
 
@@ -235,6 +192,7 @@ public class AittUnitTest {
    public void testAittConstructor_P(){
       String id = "aitt";
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, id);
          assertNotNull("Aitt Instance not null", aitt);
       } catch(Exception e) {
@@ -246,6 +204,7 @@ public class AittUnitTest {
    public void testInitializeInvalidId_N() {
       String _id = "";
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, _id);
          aitt.close();
       } catch(InstantiationException e) {
@@ -257,6 +216,7 @@ public class AittUnitTest {
    public void testInitializeInvalidContext_N() {
       String _id = "";
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(null, _id);
          aitt.close();
       } catch(InstantiationException e) {
@@ -266,16 +226,7 @@ public class AittUnitTest {
 
    @Test(expected = InstantiationException.class)
    public void testConstructorFail_N() throws InstantiationException {
-      try{
-         PowerMockito.replace(MemberMatcher.method(Aitt.class, "initJNI")).with(new InvocationHandler() {
-            @Override
-            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
-               return 0L;
-            }
-         });
-      } catch(Exception e) {
-         fail("Failed to replace method" + e);
-      }
+      shadowJniInterface.setInitReturn(false);
       String id = "aitt";
       Aitt aitt = new Aitt(appContext,id);
       aitt.close();
@@ -284,6 +235,7 @@ public class AittUnitTest {
    @Test
    public void testConnect_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -298,6 +250,7 @@ public class AittUnitTest {
    @Test
    public void testConnectWithoutIP_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -312,6 +265,7 @@ public class AittUnitTest {
    @Test
    public void testDisconnect_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -326,6 +280,7 @@ public class AittUnitTest {
    @Test
    public void testPublishMqtt_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -343,6 +298,7 @@ public class AittUnitTest {
    @Test
    public void testPublishWebRTC_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -360,6 +316,7 @@ public class AittUnitTest {
    @Test
    public void testPublishInvalidTopic_N(){
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
          aitt.connect(brokerIp, port);
          String _topic = "";
@@ -378,6 +335,7 @@ public class AittUnitTest {
    @Test
    public void testPublishAnyProtocol_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -395,6 +353,7 @@ public class AittUnitTest {
    @Test
    public void testPublishProtocolSet_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -413,6 +372,7 @@ public class AittUnitTest {
    @Test
    public void testPublishInvalidProtocol_N(){
       try{
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
          aitt.connect(brokerIp,port);
          byte[] payload = message.getBytes();
@@ -431,6 +391,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeMqtt_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -453,6 +414,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeWebRTC_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -473,11 +435,10 @@ public class AittUnitTest {
       }
    }
 
-
    @Test
    public void testSubscribeInvalidTopic_N() {
-
       try{
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
          aitt.connect(brokerIp, port);
 
@@ -500,6 +461,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeInvalidCallback_N() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          aitt.connect(brokerIp, port);
@@ -519,6 +481,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeAnyProtocol_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -542,6 +505,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeInvalidProtocol_N() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          aitt.connect(brokerIp, port);
@@ -567,6 +531,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeProtocolSet_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -591,6 +556,7 @@ public class AittUnitTest {
    @Test
    public void testUnsubscribe_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -612,6 +578,7 @@ public class AittUnitTest {
    @Test
    public void testUnsubscribeInvalidTopic_N() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          aitt.connect(brokerIp, port);
@@ -630,6 +597,7 @@ public class AittUnitTest {
    @Test
    public void testSetConnectionCallback_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -657,6 +625,7 @@ public class AittUnitTest {
    @Test
    public void testSetConnectionCallbackInvalidCallback_N() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertThrows(IllegalArgumentException.class, () -> {
@@ -673,6 +642,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeMultipleCallbacks_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -695,6 +665,7 @@ public class AittUnitTest {
    @Test
    public void testDiscoveryMessageCallbackConnected_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -716,6 +687,7 @@ public class AittUnitTest {
    @Test
    public void testDiscoveryMessageCallbackDisconnected_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -737,6 +709,7 @@ public class AittUnitTest {
    @Test
    public void testDiscoveryMessageCallbackEmptyPayload_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
 
          assertNotNull("Aitt Instance not null", aitt);
@@ -754,6 +727,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeCallbackVerifyTopic_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
          aitt.connect(brokerIp, port);
 
@@ -776,6 +750,7 @@ public class AittUnitTest {
    @Test
    public void testSubscribeCallbackVerifyPayload_P() {
       try {
+         shadowJniInterface.setInitReturn(true);
          Aitt aitt = new Aitt(appContext, aittId);
          aitt.connect(brokerIp, port);
 
diff --git a/android/aitt/src/test/java/com/samsung/android/aitt/ShadowJniInterface.java b/android/aitt/src/test/java/com/samsung/android/aitt/ShadowJniInterface.java
new file mode 100644 (file)
index 0000000..71092d2
--- /dev/null
@@ -0,0 +1,44 @@
+package com.samsung.android.aitt;
+
+import com.samsung.android.aittnative.JniInterface;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(JniInterface.class)
+public class ShadowJniInterface {
+    private static boolean initReturn = true;
+
+    @Implementation
+    public long init(String id, String ip, boolean clearSession){
+        if(initReturn)
+            return 1L;
+        else
+            return 0L;
+    }
+
+    @Implementation
+    public void connect(long instance, final String host, int port){
+    }
+
+    @Implementation
+    public void publish(long instance, final String topic, final byte[] data, long datalen, int protocol, int qos, boolean retain){
+    }
+
+    @Implementation
+    public void disconnect(long instance){
+
+    }
+    @Implementation
+    public long subscribe(long instance, final String topic, int protocol, int qos){
+        return 1L;
+    }
+
+    @Implementation
+    public void unsubscribe(long instance, final long aittSubId){
+    }
+
+    public void setInitReturn(boolean initReturn) {
+        this.initReturn = initReturn;
+    }
+}
index 2869ff7..26408cb 100644 (file)
@@ -3,3 +3,4 @@ include ':android:flatbuffers'
 include ':android:mosquitto'
 include ':android:modules:tcp'
 include ':android:modules:webrtc'
+include ':android:aitt-native'