Adroid test-server client
This commit is contained in:
parent
3358c53445
commit
d6fba75433
26 changed files with 2705 additions and 0 deletions
51
test-server/android/README
Normal file
51
test-server/android/README
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* libwebsockets Android client - libwebsockets test application for Android
|
||||
*
|
||||
* Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
This directory contains an Android Studio (2.1.1) project that builds
|
||||
libwebsockets (+ openssl + zlib) and an Android application that is able
|
||||
to connect to the 'dumb-increment-protocol' of the libwebsockets test server.
|
||||
|
||||
Building the native libraries requires the Android NDK which can be
|
||||
installed using the SDK manager.
|
||||
|
||||
The app/src/main/jni/NativeLibs.mk is fully integraded with Gradle but will
|
||||
only work on Linux and requires the following applications to be available
|
||||
in addition to the NDK:
|
||||
|
||||
awk cmake egrep git tar wget makedepend
|
||||
|
||||
(makedepend can be installed from (Debian) xutils-dev)
|
||||
|
||||
To build the project:
|
||||
|
||||
- Open an 'existing project' with Android Studio and select this directory.
|
||||
(answer yes/ok to the question to integrate with Gradle).
|
||||
|
||||
- Open the file app/src/main/jni/Application.mk and make sure NDK_ROOT
|
||||
is set correctly and that APP_PLATFORM is set to the appropriate API level.
|
||||
|
||||
- Build the project with CTRL+F9
|
||||
(open the gradle console to follow the build progress).
|
||||
|
||||
- Install APK to device and run.
|
||||
|
||||
- Connect to libwebsockets test server.
|
||||
|
||||
|
115
test-server/android/app/app.iml
Normal file
115
test-server/android/app/app.iml
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LwsClient" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.3.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.3.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.3.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.3.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-vector-drawable-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="animated-vector-drawable-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-23.3.0" level="project" />
|
||||
</component>
|
||||
</module>
|
42
test-server/android/app/build.gradle
Normal file
42
test-server/android/app/build.gradle
Normal file
|
@ -0,0 +1,42 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.libwebsockets.client"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jni.srcDirs = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
}
|
||||
|
||||
task buildNativeLibs(type: Exec, description: "compile the native libraries") {
|
||||
commandLine 'make', '-f', 'NativeLibs.mk', '-C', 'src/main/jni', 'all'
|
||||
}
|
||||
|
||||
task cleanNativeLibs(type: Exec, description: "clean the native libraries source tree") {
|
||||
commandLine 'make', '-f', 'NativeLibs.mk', '-C', 'src/main/jni', 'clean-ndk'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn buildNativeLibs }
|
||||
clean.dependsOn 'cleanNativeLibs'
|
29
test-server/android/app/src/main/AndroidManifest.xml
Normal file
29
test-server/android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.libwebsockets.client">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:launchMode="singleTop"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name="org.libwebsockets.client.MainActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustPan">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="org.libwebsockets.client.LwsService" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* LwsService.java - libwebsockets test service for Android
|
||||
*
|
||||
* Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
package org.libwebsockets.client;
|
||||
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
public class LwsService extends ThreadService {
|
||||
|
||||
/**
|
||||
* Commands that can be send to this service
|
||||
*/
|
||||
public final static int MSG_SET_CONNECTION_PARAMETERS = 1;
|
||||
|
||||
/**
|
||||
* Messages that may be send to output Messenger
|
||||
* Clients should handle these messages.
|
||||
**/
|
||||
public final static int MSG_DUMB_INCREMENT_PROTOCOL_COUNTER = 1;
|
||||
public final static int MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 2;
|
||||
public final static int MSG_LWS_CALLBACK_CLIENT_ESTABLISHED = 3;
|
||||
|
||||
public static class ConnectionParameters {
|
||||
String serverAddress;
|
||||
int serverPort;
|
||||
|
||||
ConnectionParameters(
|
||||
String serverAddress,
|
||||
int serverPort
|
||||
){
|
||||
this.serverAddress = serverAddress;
|
||||
this.serverPort = serverPort;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming messages from clients of this service
|
||||
*/
|
||||
@Override
|
||||
public void handleInputMessage(Message msg) {
|
||||
Message m;
|
||||
switch(msg.what) {
|
||||
case MSG_SET_CONNECTION_PARAMETERS: {
|
||||
LwsService.ConnectionParameters parameters = (ConnectionParameters) msg.obj;
|
||||
setConnectionParameters(
|
||||
parameters.serverAddress,
|
||||
parameters.serverPort
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
super.handleInputMessage(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The run() function for the thread.
|
||||
* For this test we implement a very long lived task
|
||||
* that sends many messages back to the client.
|
||||
* **/
|
||||
public void workerThreadRun() {
|
||||
|
||||
initLws();
|
||||
connectLws();
|
||||
|
||||
while(true) {
|
||||
|
||||
// service the websockets
|
||||
serviceLws();
|
||||
|
||||
// Check if we must quit or suspend
|
||||
synchronized (mThreadLock){
|
||||
while(mMustSuspend) {
|
||||
// We are asked to suspend the thread
|
||||
try {
|
||||
mThreadLock.wait();
|
||||
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
if(mMustQuit) {
|
||||
// The signal to quit was given
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Throttle the loop so that it iterates once every 50ms
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
exitLws();
|
||||
}
|
||||
|
||||
/** Load the native libwebsockets code */
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("lwsservice");
|
||||
}
|
||||
catch(UnsatisfiedLinkError ule) {
|
||||
Log.e("LwsService", "Warning: Could not load native library: " + ule.getMessage());
|
||||
}
|
||||
}
|
||||
public native boolean initLws();
|
||||
public native void exitLws();
|
||||
public native void serviceLws();
|
||||
public native void setConnectionParameters(String serverAddress, int serverPort);
|
||||
public native boolean connectLws();
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* MainActivity.java - libwebsockets test service for Android
|
||||
*
|
||||
* Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
package org.libwebsockets.client;
|
||||
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements LwsService.OutputInterface {
|
||||
|
||||
/** This is the Messenger that handles output from the Service */
|
||||
private Messenger mMessenger = null;
|
||||
|
||||
/** The Messenger for sending commands to the Service */
|
||||
private Messenger mService = null;
|
||||
private ServiceConnection mServiceConnection = null;
|
||||
|
||||
private boolean mThreadIsRunning = false;
|
||||
private boolean mThreadIsSuspended = false;
|
||||
|
||||
private TextView tvCounter;
|
||||
private EditText etServer;
|
||||
private EditText etPort;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Get the layout items
|
||||
tvCounter = (TextView) findViewById(R.id.textView_counter);
|
||||
etServer = (EditText) findViewById(R.id.editText_serverLocation);
|
||||
etPort = (EditText) findViewById(R.id.editText_portNumber);
|
||||
|
||||
// Create the Messenger for handling output from the service
|
||||
mMessenger = new Messenger(new LwsService.OutputHandler(this));
|
||||
|
||||
// Restore state from the Bundle when restarting due to a device change.
|
||||
if(savedInstanceState!=null) {
|
||||
mThreadIsRunning = savedInstanceState.getBoolean("mThreadIsRunning");
|
||||
}
|
||||
|
||||
mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = new Messenger(service);
|
||||
try {
|
||||
// Set the output messenger by starting the thread
|
||||
Message msg = Message.obtain(null, LwsService.MSG_SET_OUTPUT_HANDLER, 0, 0);
|
||||
msg.replyTo = mMessenger;
|
||||
mService.send(msg);
|
||||
if(mThreadIsRunning){
|
||||
// If the thread is already running at this point it means
|
||||
// that the application was restarted after a device change.
|
||||
// This implies that the thread was suspended by the onStop method.
|
||||
msg = Message.obtain(null, LwsService.MSG_THREAD_RESUME, 0, 0);
|
||||
mService.send(msg);
|
||||
mThreadIsSuspended = false;
|
||||
}
|
||||
}
|
||||
catch(RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.e("MainActivity","onServiceDisconnected !");
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
|
||||
if(savedInstanceState==null){
|
||||
startService(new Intent(getBaseContext(), LwsService.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean("mThreadIsRunning", mThreadIsRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
bindService(new Intent(getBaseContext(), LwsService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if(mThreadIsRunning) {
|
||||
if (!mThreadIsSuspended) {
|
||||
try {
|
||||
mService.send(Message.obtain(null, LwsService.MSG_THREAD_SUSPEND, 0, 0));
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mThreadIsSuspended = true;
|
||||
}
|
||||
}
|
||||
unbindService(mServiceConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if(isFinishing()){
|
||||
stopService(new Intent(getBaseContext(), LwsService.class));
|
||||
}
|
||||
}
|
||||
|
||||
/** Implement the interface to receive output from the LwsService */
|
||||
@Override
|
||||
public void handleOutputMessage(Message message) {
|
||||
switch(message.what) {
|
||||
case LwsService.MSG_DUMB_INCREMENT_PROTOCOL_COUNTER:
|
||||
tvCounter.setText((String)message.obj);
|
||||
break;
|
||||
case LwsService.MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
connectErrorListener();
|
||||
break;
|
||||
case LwsService.MSG_LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
break;
|
||||
case LwsService.MSG_THREAD_STARTED:
|
||||
// The thread was started
|
||||
mThreadIsRunning = true;
|
||||
mThreadIsSuspended = false;
|
||||
break;
|
||||
case LwsService.MSG_THREAD_STOPPED:
|
||||
// The thread was stopped
|
||||
mThreadIsRunning = false;
|
||||
mThreadIsSuspended = false;
|
||||
break;
|
||||
case LwsService.MSG_THREAD_SUSPENDED:
|
||||
// The thread is suspended
|
||||
mThreadIsRunning = true;
|
||||
mThreadIsSuspended = true;
|
||||
break;
|
||||
case LwsService.MSG_THREAD_RESUMED:
|
||||
// the thread was resumed
|
||||
mThreadIsRunning = true;
|
||||
mThreadIsSuspended = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void connectErrorListener(){
|
||||
try {
|
||||
Message msg;
|
||||
if(mThreadIsRunning) {
|
||||
msg = Message.obtain(null, LwsService.MSG_THREAD_STOP);
|
||||
mService.send(msg);
|
||||
}
|
||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
||||
adb.setTitle("Error");
|
||||
adb.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
adb.setMessage("Could not connect to the server.");
|
||||
adb.show();
|
||||
}
|
||||
catch (RemoteException e){}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start/Stop Button Handler
|
||||
*/
|
||||
|
||||
public void clickStart(View v) {
|
||||
if(!mThreadIsRunning) {
|
||||
View view = this.getCurrentFocus();
|
||||
if (view != null) {
|
||||
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
mThreadIsRunning = true;
|
||||
mThreadIsSuspended = false;
|
||||
try {
|
||||
Message msg = Message.obtain(null, LwsService.MSG_SET_CONNECTION_PARAMETERS, 0, 0);
|
||||
int port = 0;
|
||||
if(!etPort.getText().toString().equals("")) // prevent NumberformatException
|
||||
port = Integer.parseInt(etPort.getText().toString());
|
||||
LwsService.ConnectionParameters parameters = new LwsService.ConnectionParameters(
|
||||
etServer.getText().toString(),
|
||||
port
|
||||
);
|
||||
msg.obj = parameters;
|
||||
mService.send(msg);
|
||||
msg = Message.obtain(null, LwsService.MSG_THREAD_START, 0, 0);
|
||||
mService.send(msg);
|
||||
}
|
||||
catch(RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
mService.send(Message.obtain(null, LwsService.MSG_THREAD_STOP, 0, 0));
|
||||
}
|
||||
catch(RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mThreadIsRunning = false;
|
||||
mThreadIsSuspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* ThreadService.java - libwebsockets test service for Android
|
||||
*
|
||||
* Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
package org.libwebsockets.client;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class ThreadService extends Service {
|
||||
/** Messages that can be send to the Service: **/
|
||||
public final static int MSG_SET_OUTPUT_HANDLER = 1001;
|
||||
public final static int MSG_THREAD_START = 1002;
|
||||
public final static int MSG_THREAD_STOP = 1003;
|
||||
public final static int MSG_THREAD_SUSPEND = 1004;
|
||||
public final static int MSG_THREAD_RESUME = 1005;
|
||||
|
||||
/**
|
||||
* Messages that may be send from the Service
|
||||
* (Clients should handle these messages)
|
||||
**/
|
||||
public final static int MSG_THREAD_STARTED = 2001;
|
||||
public final static int MSG_THREAD_STOPPED = 2002;
|
||||
public final static int MSG_THREAD_SUSPENDED = 2003;
|
||||
public final static int MSG_THREAD_RESUMED = 2004;
|
||||
|
||||
/** Data accessed by both worker thread and the UI-thread must be synchronized **/
|
||||
public final Object mThreadLock = new Object();;
|
||||
public volatile boolean mMustQuit;
|
||||
public volatile boolean mWorkThreadIsRunning;
|
||||
public volatile boolean mMustSuspend;
|
||||
|
||||
/** Handler for incoming messages **/
|
||||
public static class InputHandler extends Handler {
|
||||
private final WeakReference<ThreadService> mService;
|
||||
InputHandler(ThreadService service) {
|
||||
mService = new WeakReference<ThreadService>(service);
|
||||
}
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
ThreadService service = mService.get();
|
||||
if(service != null) {
|
||||
service.handleInputMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface and Handler for outgoing messages to clients of this service.
|
||||
* (Must be implemented by the client.)
|
||||
*/
|
||||
public interface OutputInterface {
|
||||
void handleOutputMessage(Message message);
|
||||
}
|
||||
public static class OutputHandler extends Handler {
|
||||
// Notice that we do NOT use a WeakReference here
|
||||
// (If we did the service would lose mOutputMessenger the moment
|
||||
// that garbage collection is performed by the Java VM)
|
||||
private final OutputInterface mInterface;
|
||||
OutputHandler(OutputInterface object) {
|
||||
mInterface = object;
|
||||
}
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
mInterface.handleOutputMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/** The Messengers used to communicate with the clients of this service **/
|
||||
public final Messenger mInputMessenger = new Messenger(new InputHandler(this));
|
||||
public Messenger mOutputMessenger;
|
||||
|
||||
/** The worker thread and its runnable **/
|
||||
public static class WorkerThreadRunnable implements Runnable {
|
||||
private final WeakReference<ThreadService> mService;
|
||||
WorkerThreadRunnable(ThreadService service){
|
||||
mService = new WeakReference<ThreadService>(service);
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadService service = mService.get();
|
||||
if(service != null) {
|
||||
service.mWorkThreadIsRunning = true;
|
||||
service.workerThreadRun();
|
||||
service.mWorkThreadIsRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public Thread mWorkerThread;
|
||||
|
||||
/** Handle incoming messages from the client **/
|
||||
public void handleInputMessage(Message msg) {
|
||||
try {
|
||||
Message m;
|
||||
switch(msg.what) {
|
||||
case MSG_SET_OUTPUT_HANDLER:
|
||||
// set the output messenger then
|
||||
// send a message indicating the thread status
|
||||
mOutputMessenger = msg.replyTo;
|
||||
break;
|
||||
case MSG_THREAD_START:
|
||||
try {
|
||||
// reset thread vars
|
||||
synchronized (mThreadLock) {
|
||||
// thread allready running?
|
||||
if(!mWorkThreadIsRunning){
|
||||
// no, start it
|
||||
mMustQuit = false;
|
||||
mMustSuspend = false;
|
||||
mWorkerThread = new Thread(new WorkerThreadRunnable(this));
|
||||
mWorkerThread.start();
|
||||
}
|
||||
else {
|
||||
// yes, resume it
|
||||
mMustQuit = false;
|
||||
mMustSuspend = false;
|
||||
mThreadLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(mOutputMessenger != null) {
|
||||
m = Message.obtain(null, MSG_THREAD_STARTED, 0, 0);
|
||||
mOutputMessenger.send(m);
|
||||
}
|
||||
break;
|
||||
case MSG_THREAD_STOP:
|
||||
try {
|
||||
synchronized(mThreadLock) {
|
||||
if(mWorkThreadIsRunning) {
|
||||
mMustQuit = true;
|
||||
mMustSuspend = false;
|
||||
mThreadLock.notifyAll();
|
||||
}
|
||||
}
|
||||
mWorkerThread.join();
|
||||
}
|
||||
catch(InterruptedException e) {
|
||||
Log.e("ThreadService","handleInputMessage join() interrupted");
|
||||
}
|
||||
if(mOutputMessenger != null) {
|
||||
m = Message.obtain(null, MSG_THREAD_STOPPED, 0, 0);
|
||||
mOutputMessenger.send(m);
|
||||
}
|
||||
break;
|
||||
case MSG_THREAD_SUSPEND:
|
||||
synchronized (mThreadLock) {
|
||||
if(mWorkThreadIsRunning) {
|
||||
mMustSuspend = true;
|
||||
}
|
||||
}
|
||||
if(mOutputMessenger != null) {
|
||||
m = Message.obtain(null, MSG_THREAD_SUSPENDED, 0, 0);
|
||||
mOutputMessenger.send(m);
|
||||
}
|
||||
break;
|
||||
case MSG_THREAD_RESUME:
|
||||
synchronized (mThreadLock) {
|
||||
if(mWorkThreadIsRunning) {
|
||||
mMustSuspend = false;
|
||||
mThreadLock.notifyAll();
|
||||
}
|
||||
}
|
||||
if(mOutputMessenger != null) {
|
||||
m = Message.obtain(null, MSG_THREAD_RESUMED, 0, 0);
|
||||
mOutputMessenger.send(m);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be called from the JNI functions to send output messages to the client
|
||||
*/
|
||||
public void sendMessage(int msg, Object obj){
|
||||
Message m = Message.obtain(null, msg, 0, 0);
|
||||
m.obj = obj;
|
||||
try {
|
||||
mOutputMessenger.send(m);
|
||||
}
|
||||
catch(RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/** The run() function for the worker thread **/
|
||||
public abstract void workerThreadRun();
|
||||
|
||||
/**
|
||||
* Called when the service is being created.
|
||||
* ie. When the first client calls bindService() or startService().
|
||||
**/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// initialize variables
|
||||
mWorkThreadIsRunning = false;
|
||||
mMustQuit = false;
|
||||
mOutputMessenger = null;
|
||||
mWorkerThread = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the first client is binding to the service with bindService()
|
||||
*
|
||||
* If the service was started with bindService() it will automatically stop when the last
|
||||
* client unbinds from the service. If you want the service to continue running even if it
|
||||
* is not bound to anything then start the service with startService() before
|
||||
* calling bindService(). In this case stopService() must be called after unbinding
|
||||
* to stop the service.
|
||||
*/
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mInputMessenger.getBinder();
|
||||
}
|
||||
|
||||
/** Called if the service is started with startService(). */
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
/** Called when the first client is binds to the service with bindService() */
|
||||
@Override
|
||||
public void onRebind(Intent intent) {}
|
||||
|
||||
/** Called when all clients have unbound with unbindService() */
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
//mOutputMessenger = null;
|
||||
return false; // do not allow to rebind.
|
||||
}
|
||||
|
||||
/** Called when the service is no longer used and is being destroyed */
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
try {
|
||||
if(mWorkThreadIsRunning){
|
||||
synchronized(mThreadLock) {
|
||||
mMustQuit = true;
|
||||
mMustSuspend = false;
|
||||
mThreadLock.notifyAll();
|
||||
}
|
||||
mWorkerThread.join();
|
||||
}
|
||||
}
|
||||
catch(NullPointerException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
41
test-server/android/app/src/main/jni/Android.mk
Normal file
41
test-server/android/app/src/main/jni/Android.mk
Normal file
|
@ -0,0 +1,41 @@
|
|||
# get current directory
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# libz.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libz
|
||||
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/lib/libz.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
# libssl.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libssl
|
||||
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/lib/libssl.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
# libcrypto.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libcrypto
|
||||
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/lib/libcrypto.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
# libwebsockets.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libwebsockets
|
||||
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/lib/libwebsockets.a
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
|
||||
# liblwsservice.so
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true
|
||||
LOCAL_MODULE := lwsservice
|
||||
LOCAL_SRC_FILES := LwsService.cpp
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(TARGET_ARCH_ABI)/include
|
||||
LOCAL_STATIC_LIBRARIES := websockets z ssl crypto
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_SHARED_LIBRARY)
|
49
test-server/android/app/src/main/jni/Application.mk
Normal file
49
test-server/android/app/src/main/jni/Application.mk
Normal file
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# Zlib, OpenSSL and libwebsockets will be downloaded automatically unless you place
|
||||
# their source .tar.gz files in the jni directory...
|
||||
#
|
||||
|
||||
# The location of the NDK
|
||||
#
|
||||
NDK_ROOT := /opt/Android/Sdk/ndk-bundle
|
||||
|
||||
# Update these to the latest versions before building
|
||||
#
|
||||
ZLIB_VERSION := 1.2.8
|
||||
OPENSSL_VERSION := 1.0.2h
|
||||
|
||||
# This will be executed as 'git clone $(LIBWEBSOCKETS_GIT_URL)'
|
||||
#
|
||||
LIBWEBSOCKETS_GIT_URL := --branch master https://github.com/warmcat/libwebsockets.git
|
||||
|
||||
#
|
||||
# Note: If you build for API level 21 or higher in APP_PLATFORM,
|
||||
# the resulting application will only run on API 21+ devices.
|
||||
# Even if minSdkVersion has been set to a lower level!
|
||||
# This is the result of API changes for the native signal() function.
|
||||
# The recommended solution is to build two packages, one for API 17+ and the other for API 21+ devices.
|
||||
# http://stackoverflow.com/questions/28740315/android-ndk-getting-java-lang-unsatisfiedlinkerror-dlopen-failed-cannot-loca
|
||||
#
|
||||
# Note: If you change the API level the JNI code must be rebuild completely.
|
||||
# (Run 'make clean' from the app/src/main/jni directory.)
|
||||
#
|
||||
APP_PLATFORM := android-23
|
||||
|
||||
# Builds for armeabi armeabi-v7a x86 mips arm64-v8a x86_64 mips64
|
||||
#
|
||||
#APP_ABI := all
|
||||
|
||||
# The same as above.
|
||||
#
|
||||
#APP_ABI := armeabi armeabi-v7a x86 mips arm64-v8a x86_64 mips64
|
||||
|
||||
# Good enough for most current devices + x86 AVD
|
||||
#
|
||||
APP_ABI := armeabi-v7a x86
|
||||
|
||||
# Enable (GNU) c++11 extentions
|
||||
APP_CPPFLAGS += -std=gnu++11
|
||||
|
||||
# Use the GNU standard template library
|
||||
APP_STL := gnustl_shared
|
||||
|
307
test-server/android/app/src/main/jni/LwsService.cpp
Normal file
307
test-server/android/app/src/main/jni/LwsService.cpp
Normal file
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* LwsService.cpp - libwebsockets test service for Android
|
||||
*
|
||||
* Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* The test apps are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#define printf(...) __android_log_print(ANDROID_LOG_VERBOSE, "LwsService", ##__VA_ARGS__)
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Code executed when loading the dynamic link library //
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
// The Java class the native functions shall be part of
|
||||
#define JNIREG_CLASS "org/libwebsockets/client/LwsService"
|
||||
|
||||
JavaVM* gJvm = NULL;
|
||||
JNIEnv* gEnv = 0;
|
||||
|
||||
JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj);
|
||||
JNIEXPORT void JNICALL jni_exitLws(JNIEnv *env, jobject obj);
|
||||
JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj);
|
||||
JNIEXPORT void JNICALL jni_setConnectionParameters(JNIEnv *env, jobject obj, jstring serverAddress, jint serverPort);
|
||||
JNIEXPORT jboolean JNICALL jni_connectLws(JNIEnv *env, jobject obj);
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
{ "initLws", "()Z", (void*)jni_initLws },
|
||||
{ "exitLws", "()V", (void*)jni_exitLws },
|
||||
{ "serviceLws", "()V", (void*)jni_serviceLws },
|
||||
{ "setConnectionParameters", "(Ljava/lang/String;I)V", (void*)jni_setConnectionParameters },
|
||||
{ "connectLws", "()Z", (void*)jni_connectLws },
|
||||
};
|
||||
|
||||
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods)
|
||||
{
|
||||
jclass cls;
|
||||
cls = env->FindClass(className);
|
||||
if(cls == NULL) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (env->RegisterNatives(cls, gMethods, numMethods) < 0) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
static int registerNatives(JNIEnv* env)
|
||||
{
|
||||
if(!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void * reserved) {
|
||||
jint result = -1;
|
||||
|
||||
gJvm = vm;
|
||||
if(vm->GetEnv((void**)&gEnv, JNI_VERSION_1_6) != JNI_OK) goto bail;
|
||||
if(vm->AttachCurrentThread(&gEnv, NULL) < 0) goto bail;
|
||||
if(registerNatives(gEnv) != JNI_TRUE) goto bail;
|
||||
|
||||
result = JNI_VERSION_1_6;
|
||||
|
||||
bail:
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
gJvm = NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// JNI functions to export: //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
static jclass gLwsServiceCls;
|
||||
static jobject gLwsServiceObj;
|
||||
static jmethodID sendMessageId;
|
||||
|
||||
static const int MSG_DUMB_INCREMENT_PROTOCOL_COUNTER = 1;
|
||||
static const int MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 2;
|
||||
static const int MSG_LWS_CALLBACK_CLIENT_ESTABLISHED = 3;
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
static struct lws_context *context = NULL;
|
||||
static struct lws_context_creation_info info;
|
||||
static struct lws *wsi = NULL;
|
||||
|
||||
// prevents sending messages after jni_exitLws had been called
|
||||
static int isExit = 0;
|
||||
|
||||
enum websocket_protocols {
|
||||
PROTOCOL_DUMB_INCREMENT = 0,
|
||||
PROTOCOL_COUNT
|
||||
};
|
||||
|
||||
struct per_session_data {
|
||||
;// no data
|
||||
};
|
||||
|
||||
static int callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len );
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{
|
||||
"dumb-increment-protocol",
|
||||
callback,
|
||||
sizeof( struct per_session_data ),
|
||||
BUFFER_SIZE,
|
||||
},
|
||||
{ NULL, NULL, 0, 0 } // end of list
|
||||
};
|
||||
|
||||
static const struct lws_extension exts[] = {
|
||||
{
|
||||
"deflate-frame",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"deflate_frame"
|
||||
},
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
static int port = 0;
|
||||
static int use_ssl = 0;
|
||||
static int use_ssl_client = 0;
|
||||
static char address[8192];
|
||||
|
||||
static char ca_cert[8192];
|
||||
static char client_cert[8192];
|
||||
static char client_cert_key[8192];
|
||||
|
||||
static int deny_deflate = 0;
|
||||
static int deny_mux = 0;
|
||||
|
||||
// Logging function for libwebsockets
|
||||
static void emit_log(int level, const char *msg)
|
||||
{
|
||||
printf("%s", msg);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jboolean JNICALL jni_initLws(JNIEnv *env, jobject obj)
|
||||
{
|
||||
if(context) return JNI_TRUE;
|
||||
|
||||
// Attach the java virtual machine to this thread
|
||||
gJvm->AttachCurrentThread(&gEnv, NULL);
|
||||
|
||||
// Set java global references to the class and object
|
||||
jclass cls = env->GetObjectClass(obj);
|
||||
gLwsServiceCls = (jclass) env->NewGlobalRef(cls);
|
||||
gLwsServiceObj = env->NewGlobalRef(obj);
|
||||
|
||||
// Get the sendMessage method from the LwsService class (inherited from class ThreadService)
|
||||
sendMessageId = gEnv->GetMethodID(gLwsServiceCls, "sendMessage", "(ILjava/lang/Object;)V");
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.port = CONTEXT_PORT_NO_LISTEN;
|
||||
info.protocols = protocols;
|
||||
#ifndef LWS_NO_EXTENSIONS
|
||||
info.extensions = exts;
|
||||
#endif
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
|
||||
lws_set_log_level( LLL_NOTICE | LLL_INFO | LLL_ERR | LLL_WARN | LLL_CLIENT, emit_log );
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if( context == NULL ){
|
||||
emit_log(LLL_ERR, "Creating libwebsocket context failed");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
isExit = 0;
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
// Send a message to the client of the service
|
||||
// (must call jni_initLws() first)
|
||||
static inline void sendMessage(int id, jobject obj)
|
||||
{
|
||||
if(!isExit) gEnv->CallVoidMethod(gLwsServiceObj, sendMessageId, id, obj);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_exitLws(JNIEnv *env, jobject obj)
|
||||
{
|
||||
if(context){
|
||||
isExit = 1;
|
||||
lws_context_destroy(context);
|
||||
context = NULL;
|
||||
env->DeleteGlobalRef(gLwsServiceObj);
|
||||
env->DeleteGlobalRef(gLwsServiceCls);
|
||||
}
|
||||
}
|
||||
|
||||
static int callback(
|
||||
struct lws *wsi,
|
||||
enum lws_callback_reasons reason,
|
||||
void *user,
|
||||
void *in,
|
||||
size_t len
|
||||
)
|
||||
{
|
||||
switch(reason){
|
||||
|
||||
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
sendMessage(MSG_LWS_CALLBACK_CLIENT_CONNECTION_ERROR, NULL);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
sendMessage(MSG_LWS_CALLBACK_CLIENT_ESTABLISHED, NULL);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
((char *)in)[len] = '\0';
|
||||
sendMessage(MSG_DUMB_INCREMENT_PROTOCOL_COUNTER, gEnv->NewStringUTF((const char*)in));
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
|
||||
if ((strcmp((const char*)in, "deflate-stream") == 0) && deny_deflate) {
|
||||
emit_log(LLL_ERR, "websocket: denied deflate-stream extension");
|
||||
return 1;
|
||||
}
|
||||
if ((strcmp((const char*)in, "deflate-frame") == 0) && deny_deflate) {
|
||||
emit_log(LLL_ERR, "websocket: denied deflate-frame extension");
|
||||
return 1;
|
||||
}
|
||||
if ((strcmp((const char*)in, "x-google-mux") == 0) && deny_mux) {
|
||||
emit_log(LLL_ERR, "websocket: denied x-google-mux extension");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_serviceLws(JNIEnv *env, jobject obj)
|
||||
{
|
||||
if(context){
|
||||
lws_service( context, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL jni_setConnectionParameters(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jstring serverAddress,
|
||||
jint serverPort
|
||||
)
|
||||
{
|
||||
address[0] = 0;
|
||||
port = serverPort;
|
||||
use_ssl = 0;
|
||||
use_ssl_client = 0;
|
||||
snprintf(address, sizeof(address), "%s", env->GetStringUTFChars(serverAddress, 0));
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL jni_connectLws(JNIEnv *env, jobject obj)
|
||||
{
|
||||
struct lws_client_connect_info info_ws;
|
||||
memset(&info_ws, 0, sizeof(info_ws));
|
||||
|
||||
info_ws.port = port;
|
||||
info_ws.address = address;
|
||||
info_ws.path = "/";
|
||||
info_ws.context = context;
|
||||
info_ws.ssl_connection = use_ssl;
|
||||
info_ws.host = address;
|
||||
info_ws.origin = address;
|
||||
info_ws.ietf_version_or_minus_one = -1;
|
||||
info_ws.client_exts = exts;
|
||||
info_ws.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
|
||||
|
||||
// connect
|
||||
wsi = lws_client_connect_via_info(&info_ws);
|
||||
if(wsi == NULL ){
|
||||
// Error
|
||||
emit_log(LLL_ERR, "Protocol failed to connect.");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
1257
test-server/android/app/src/main/jni/NativeLibs.mk
Normal file
1257
test-server/android/app/src/main/jni/NativeLibs.mk
Normal file
File diff suppressed because it is too large
Load diff
0
test-server/android/app/src/main/libs/placeholder
Normal file
0
test-server/android/app/src/main/libs/placeholder
Normal file
BIN
test-server/android/app/src/main/res/drawable/warmcat.png
Normal file
BIN
test-server/android/app/src/main/res/drawable/warmcat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scrollView"
|
||||
tools:context="org.libwebsockets.client.MainActivity"
|
||||
android:theme="@android:style/Theme.Holo.Light"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/imageView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/warmcat" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/editText_serverLocation"
|
||||
android:hint="Hostname or IP address"
|
||||
android:background="@android:drawable/editbox_background"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat"
|
||||
android:singleLine="true"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_below="@+id/imageView"
|
||||
android:layout_alignParentStart="true" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:ems="10"
|
||||
android:id="@+id/editText_portNumber"
|
||||
android:background="@android:drawable/editbox_background"
|
||||
android:hint="Port number"
|
||||
android:text="7681"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat"
|
||||
android:singleLine="true"
|
||||
android:layout_below="@+id/editText_serverLocation"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="(dis)connect"
|
||||
android:id="@+id/button"
|
||||
android:textAppearance="@style/Base.TextAppearance.AppCompat"
|
||||
android:onClick="clickStart"
|
||||
android:layout_below="@+id/editText_portNumber"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="dumb-increment-protocol"
|
||||
android:id="@+id/textView_counter"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@android:drawable/alert_light_frame"
|
||||
android:singleLine="false"
|
||||
android:layout_below="@+id/button" />
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
BIN
test-server/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
test-server/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
test-server/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
test-server/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
6
test-server/android/app/src/main/res/values/colors.xml
Normal file
6
test-server/android/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
5
test-server/android/app/src/main/res/values/dimens.xml
Normal file
5
test-server/android/app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
5
test-server/android/app/src/main/res/values/strings.xml
Normal file
5
test-server/android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<resources>
|
||||
<string name="app_name">libwebsockets Android Client</string>
|
||||
<string name="start">Start/Stop thread</string>
|
||||
<string name="suspend">Suspend/Resume thread</string>
|
||||
</resources>
|
11
test-server/android/app/src/main/res/values/styles.xml
Normal file
11
test-server/android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
23
test-server/android/build.gradle
Normal file
23
test-server/android/build.gradle
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
20
test-server/android/gradle.properties
Normal file
20
test-server/android/gradle.properties
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
1
test-server/android/settings.gradle
Normal file
1
test-server/android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
include ':app'
|
Loading…
Add table
Reference in a new issue